298 lines
7.4 KiB
TypeScript
298 lines
7.4 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 Issue {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
interface IssueSelectorModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
onSelect: (selectedValues: number[]) => void;
|
|
initialSelectedValues?: number[]; // Previously saved selections
|
|
issues: Issue[];
|
|
}
|
|
|
|
export default function IssueSelectorModal({
|
|
visible,
|
|
onClose,
|
|
onSelect,
|
|
initialSelectedValues = [],
|
|
issues = [],
|
|
}: IssueSelectorModalProps) {
|
|
const [selectedValues, setSelectedValues] = useState<number[]>(
|
|
initialSelectedValues
|
|
);
|
|
const [search, setSearch] = useState("");
|
|
|
|
const toggleValue = (id: number) => {
|
|
setSelectedValues((prev) =>
|
|
prev.includes(id) ? prev.filter((v) => v !== id) : [...prev, id]
|
|
);
|
|
};
|
|
|
|
const filteredIssues = issues.filter((issue) =>
|
|
issue.name.toLowerCase().includes(search.toLowerCase())
|
|
);
|
|
|
|
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}/${issues.length} Selected`}</Text>
|
|
<TouchableOpacity onPress={clearSelection} disabled={!hasSelection}>
|
|
<Text
|
|
style={[
|
|
styles.clearText,
|
|
!hasSelection && styles.clearTextDisabled,
|
|
selectedValues.length > 0 && { color: "#006C4D" },
|
|
]}
|
|
>
|
|
Clear
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<ScrollView style={styles.scrollArea}>
|
|
{filteredIssues.length > 0 ? (
|
|
filteredIssues.map((issue) => (
|
|
<TouchableOpacity
|
|
key={issue.id}
|
|
style={styles.itemRow}
|
|
onPress={() => toggleValue(issue.id)}
|
|
>
|
|
<Checkbox
|
|
value={selectedValues.includes(issue.id)}
|
|
onValueChange={() => toggleValue(issue.id)}
|
|
color={
|
|
selectedValues.includes(issue.id) ? "#009E71" : undefined
|
|
}
|
|
/>
|
|
<Text style={styles.itemLabel}>{issue.name}</Text>
|
|
</TouchableOpacity>
|
|
))
|
|
) : issues.length === 0 ? (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={styles.emptyText}>No issues available</Text>
|
|
</View>
|
|
) : (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={styles.emptyText}>
|
|
No issues found for "{search}"
|
|
</Text>
|
|
</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({
|
|
emptyContainer: {
|
|
padding: 20,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
},
|
|
emptyText: {
|
|
fontSize: 16,
|
|
color: "#717B8F",
|
|
textAlign: "center",
|
|
},
|
|
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",
|
|
},
|
|
});
|