280 lines
7.0 KiB
TypeScript
280 lines
7.0 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
TextInput,
|
|
ScrollView,
|
|
TouchableOpacity,
|
|
Modal,
|
|
StyleSheet,
|
|
} from "react-native";
|
|
import Checkbox from "expo-checkbox";
|
|
import { issueConfig } from "@/constants/config";
|
|
import CloseIcon from "@/assets/icons/close.svg";
|
|
|
|
interface IssueSelectorModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
onSelect: (selectedValues: string[]) => void;
|
|
initialSelectedValues?: string[]; // Previously saved selections
|
|
}
|
|
|
|
export default function IssueSelectorModal({
|
|
visible,
|
|
onClose,
|
|
onSelect,
|
|
initialSelectedValues = [],
|
|
}: IssueSelectorModalProps) {
|
|
const [selectedValues, setSelectedValues] = useState<string[]>(
|
|
initialSelectedValues
|
|
);
|
|
const [search, setSearch] = useState("");
|
|
|
|
const toggleValue = (value: string) => {
|
|
setSelectedValues((prev) =>
|
|
prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
|
|
);
|
|
};
|
|
|
|
const filteredConfig = issueConfig
|
|
.map((group) => ({
|
|
...group,
|
|
options: group.options.filter((option) =>
|
|
option.label.toLowerCase().includes(search.toLowerCase())
|
|
),
|
|
}))
|
|
.filter((group) => group.options.length > 0);
|
|
|
|
const clearSelection = () => setSelectedValues([]);
|
|
|
|
const hasSelection = selectedValues.length > 0;
|
|
|
|
const handleSelect = () => {
|
|
if (hasSelection) {
|
|
onSelect(selectedValues);
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
// Reset to initial values when closing without saving
|
|
setSelectedValues(initialSelectedValues);
|
|
setSearch(""); // Also clear search
|
|
onClose();
|
|
};
|
|
|
|
// Reset selectedValues when modal becomes visible with new initial values
|
|
React.useEffect(() => {
|
|
if (visible) {
|
|
setSelectedValues(initialSelectedValues);
|
|
setSearch("");
|
|
}
|
|
}, [visible, initialSelectedValues]);
|
|
|
|
return (
|
|
<Modal visible={visible} animationType="slide">
|
|
<View style={styles.container}>
|
|
{/* Header */}
|
|
<View style={styles.headerBar}>
|
|
<Text>Select Issue</Text>
|
|
<TouchableOpacity onPress={handleClose}>
|
|
<CloseIcon />
|
|
</TouchableOpacity>
|
|
</View>
|
|
<View style={styles.divider}></View>
|
|
<View style={styles.header}>
|
|
<TextInput
|
|
placeholder="Search"
|
|
placeholderTextColor="#939DAE"
|
|
value={search}
|
|
onChangeText={setSearch}
|
|
style={styles.searchBar}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.counterBar}>
|
|
<Text
|
|
style={styles.counterText}
|
|
>{`${selectedValues.length}/23 Selected`}</Text>
|
|
<TouchableOpacity onPress={clearSelection} disabled={!hasSelection}>
|
|
<Text
|
|
style={[
|
|
styles.clearText,
|
|
!hasSelection && styles.clearTextDisabled,
|
|
]}
|
|
>
|
|
Clear
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<ScrollView style={styles.scrollArea}>
|
|
{filteredConfig.map((group) => (
|
|
<View key={group.category}>
|
|
<Text style={styles.category}>{group.category}</Text>
|
|
{group.options.map((option) => (
|
|
<TouchableOpacity
|
|
key={option.value}
|
|
style={styles.itemRow}
|
|
onPress={() => toggleValue(option.value)}
|
|
>
|
|
<Checkbox
|
|
value={selectedValues.includes(option.value)}
|
|
onValueChange={() => toggleValue(option.value)}
|
|
color={
|
|
selectedValues.includes(option.value)
|
|
? "#252A34"
|
|
: undefined
|
|
}
|
|
/>
|
|
<Text style={styles.itemLabel}>{option.label}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
))}
|
|
</ScrollView>
|
|
|
|
<View style={styles.buttonsContainer}>
|
|
<TouchableOpacity style={styles.cancelButton} onPress={onClose}>
|
|
<Text style={styles.cancelText}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.doneButton,
|
|
!hasSelection && styles.doneButtonDisabled,
|
|
]}
|
|
onPress={handleSelect}
|
|
disabled={!hasSelection}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.doneText,
|
|
!hasSelection && styles.doneTextDisabled,
|
|
]}
|
|
>
|
|
Select
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
buttonsContainer: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 20,
|
|
backgroundColor: "#FCFCFC",
|
|
height: 80,
|
|
borderTopWidth: 1,
|
|
borderTopColor: "#E5E9F0",
|
|
},
|
|
headerBar: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
backgroundColor: "#F9F9F9",
|
|
},
|
|
cancelButton: {
|
|
flex: 1,
|
|
height: 40,
|
|
borderWidth: 1,
|
|
borderColor: "#D8DDE7",
|
|
backgroundColor: "#F3F5F8",
|
|
borderRadius: 4,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginRight: 8,
|
|
},
|
|
doneButton: {
|
|
flex: 1,
|
|
height: 40,
|
|
borderRadius: 4,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
backgroundColor: "#00875F", // primary green
|
|
marginLeft: 8,
|
|
},
|
|
doneButtonDisabled: {
|
|
backgroundColor: "#E5E9F0", // disabled gray background
|
|
},
|
|
doneText: {
|
|
fontSize: 14,
|
|
fontWeight: "600",
|
|
color: "#FFFFFF",
|
|
},
|
|
doneTextDisabled: {
|
|
color: "#ADB4BD", // disabled gray text
|
|
},
|
|
cancelText: {
|
|
fontSize: 14,
|
|
fontWeight: "600",
|
|
color: "#252A34",
|
|
},
|
|
clearText: {
|
|
fontSize: 14,
|
|
color: "#ADB4BD",
|
|
},
|
|
clearTextDisabled: {
|
|
color: "#D8DDE7", // more muted when disabled
|
|
},
|
|
container: { flex: 1, backgroundColor: "#fff" },
|
|
header: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
backgroundColor: "#FCFCFC",
|
|
},
|
|
searchBar: {
|
|
backgroundColor: "#fff",
|
|
borderColor: "#D8DDE6",
|
|
borderWidth: 1,
|
|
borderRadius: 4,
|
|
paddingHorizontal: 12,
|
|
height: 36,
|
|
fontSize: 14,
|
|
color: "#252A34",
|
|
},
|
|
counterBar: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 12,
|
|
backgroundColor: "#FCFCFC",
|
|
},
|
|
divider: {
|
|
height: 1,
|
|
backgroundColor: "#E5E9F0",
|
|
},
|
|
counterText: {
|
|
fontSize: 14,
|
|
color: "#555",
|
|
fontWeight: "600",
|
|
},
|
|
scrollArea: { paddingHorizontal: 0, backgroundColor: "#FCFCFC" },
|
|
category: {
|
|
fontSize: 14,
|
|
color: "#252A34",
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 16,
|
|
backgroundColor: "#F9F9F9",
|
|
},
|
|
itemRow: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 16,
|
|
backgroundColor: "#FCFCFC",
|
|
},
|
|
itemLabel: {
|
|
marginLeft: 12,
|
|
fontSize: 12,
|
|
color: "#252A34",
|
|
},
|
|
});
|