Files
sgw-owner-app/components/diary/addTripModal/MaterialCostList.tsx

473 lines
13 KiB
TypeScript

import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Platform,
TextInput,
Modal,
ScrollView,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useI18n } from "@/hooks/use-i18n";
import { useThemeContext } from "@/hooks/use-theme-context";
interface TripCost {
id: string;
type: string;
amount: number;
unit: string;
cost_per_unit: number;
total_cost: number;
}
interface MaterialCostListProps {
items: TripCost[];
onChange: (items: TripCost[]) => void;
}
// Predefined cost types
const COST_TYPES = [
{ value: "fuel", label: "Nhiên liệu" },
{ value: "food", label: "Thực phẩm" },
{ value: "crew_salary", label: "Lương thuyền viên" },
{ value: "ice_salt_cost", label: "Muối đá" },
{ value: "other", label: "Khác" },
];
export default function MaterialCostList({
items,
onChange,
}: MaterialCostListProps) {
const { t } = useI18n();
const { colors } = useThemeContext();
const [typeDropdownVisible, setTypeDropdownVisible] = useState<string | null>(null);
const handleAddMaterial = () => {
const newMaterial: TripCost = {
id: Date.now().toString(),
type: "",
amount: 0,
unit: "",
cost_per_unit: 0,
total_cost: 0,
};
onChange([...items, newMaterial]);
};
const handleRemoveMaterial = (id: string) => {
onChange(items.filter((item) => item.id !== id));
};
const handleDuplicateMaterial = (material: TripCost) => {
const duplicatedMaterial: TripCost = {
id: Date.now().toString(),
type: material.type,
amount: material.amount,
unit: material.unit,
cost_per_unit: material.cost_per_unit,
total_cost: material.total_cost,
};
onChange([...items, duplicatedMaterial]);
};
const handleUpdateMaterial = (id: string, field: keyof TripCost, value: string | number) => {
onChange(
items.map((item) => {
if (item.id === id) {
const updatedItem = { ...item, [field]: value };
// Auto-calculate total_cost when amount or cost_per_unit changes
if (field === "amount" || field === "cost_per_unit") {
const amount = field === "amount" ? Number(value) : item.amount;
const costPerUnit = field === "cost_per_unit" ? Number(value) : item.cost_per_unit;
updatedItem.total_cost = amount * costPerUnit;
}
return updatedItem;
}
return item;
})
);
};
const getTypeLabel = (value: string) => {
const type = COST_TYPES.find((t) => t.value === value);
return type ? type.label : value || t("diary.selectType");
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("vi-VN").format(amount);
};
const themedStyles = {
sectionTitle: { color: colors.text },
fieldLabel: { color: colors.text },
input: {
backgroundColor: colors.card,
borderColor: colors.border,
color: colors.text,
},
dropdown: {
backgroundColor: colors.card,
borderColor: colors.border,
},
dropdownText: { color: colors.text },
placeholder: { color: colors.textSecondary },
addButton: {
borderColor: colors.primary,
},
addButtonText: { color: colors.primary },
modalOverlay: {
backgroundColor: "rgba(0, 0, 0, 0.5)",
},
modalContent: {
backgroundColor: colors.card,
},
option: {
borderBottomColor: colors.separator,
},
optionText: { color: colors.text },
};
return (
<View style={styles.container}>
<Text style={[styles.sectionTitle, themedStyles.sectionTitle]}>
{t("diary.materialCostList")}
</Text>
{/* Cost Items List */}
{items.map((cost) => (
<View key={cost.id} style={styles.costRow}>
<View style={styles.formRow}>
{/* Type Dropdown */}
<View style={[styles.inputGroup, styles.typeGroup]}>
<Text style={[styles.fieldLabel, themedStyles.fieldLabel]}>
{t("diary.costType")}
</Text>
<TouchableOpacity
style={[styles.dropdown, themedStyles.dropdown]}
onPress={() => setTypeDropdownVisible(cost.id)}
activeOpacity={0.7}
>
<Text
style={[
styles.dropdownText,
themedStyles.dropdownText,
!cost.type && themedStyles.placeholder,
]}
>
{getTypeLabel(cost.type)}
</Text>
<Ionicons
name="chevron-down"
size={16}
color={colors.textSecondary}
/>
</TouchableOpacity>
{/* Type Dropdown Modal */}
<Modal
visible={typeDropdownVisible === cost.id}
transparent
animationType="fade"
onRequestClose={() => setTypeDropdownVisible(null)}
>
<TouchableOpacity
style={[styles.modalOverlay, themedStyles.modalOverlay]}
activeOpacity={1}
onPress={() => setTypeDropdownVisible(null)}
>
<View style={[styles.modalContent, themedStyles.modalContent]}>
<ScrollView>
{COST_TYPES.map((type) => (
<TouchableOpacity
key={type.value}
style={[styles.option, themedStyles.option]}
onPress={() => {
handleUpdateMaterial(cost.id, "type", type.value);
setTypeDropdownVisible(null);
}}
>
<Text
style={[styles.optionText, themedStyles.optionText]}
>
{type.label}
</Text>
{cost.type === type.value && (
<Ionicons
name="checkmark"
size={20}
color={colors.primary}
/>
)}
</TouchableOpacity>
))}
</ScrollView>
</View>
</TouchableOpacity>
</Modal>
</View>
{/* Amount Input */}
<View style={[styles.inputGroup, styles.smallGroup]}>
<Text style={[styles.fieldLabel, themedStyles.fieldLabel]}>
{t("diary.amount")}
</Text>
<TextInput
style={[styles.input, styles.smallInput, themedStyles.input]}
value={cost.amount.toString()}
onChangeText={(value) =>
handleUpdateMaterial(cost.id, "amount", Number(value) || 0)
}
placeholder="0"
placeholderTextColor={colors.textSecondary}
keyboardType="numeric"
/>
</View>
{/* Unit Input */}
<View style={[styles.inputGroup, styles.smallGroup]}>
<Text style={[styles.fieldLabel, themedStyles.fieldLabel]}>
{t("diary.unit")}
</Text>
<TextInput
style={[styles.input, styles.smallInput, themedStyles.input]}
value={cost.unit}
onChangeText={(value) =>
handleUpdateMaterial(cost.id, "unit", value)
}
placeholder={t("diary.unitPlaceholder")}
placeholderTextColor={colors.textSecondary}
/>
</View>
</View>
<View style={styles.formRow}>
{/* Cost Per Unit Input */}
<View style={[styles.inputGroup, styles.mediumGroup]}>
<Text style={[styles.fieldLabel, themedStyles.fieldLabel]}>
{t("diary.costPerUnit")}
</Text>
<TextInput
style={[styles.input, styles.mediumInput, themedStyles.input]}
value={cost.cost_per_unit.toString()}
onChangeText={(value) =>
handleUpdateMaterial(
cost.id,
"cost_per_unit",
Number(value) || 0
)
}
placeholder="0"
placeholderTextColor={colors.textSecondary}
keyboardType="numeric"
/>
</View>
{/* Total Cost (Read-only, auto-calculated) */}
<View style={[styles.inputGroup, styles.mediumGroup]}>
<Text style={[styles.fieldLabel, themedStyles.fieldLabel]}>
{t("diary.totalCost")}
</Text>
<View style={[styles.input, styles.mediumInput, themedStyles.input, styles.readOnlyInput]}>
<Text style={[styles.readOnlyText, themedStyles.dropdownText]}>
{formatCurrency(cost.total_cost)}
</Text>
</View>
</View>
{/* Action Buttons */}
<View style={styles.actionButtons}>
<TouchableOpacity
onPress={() => handleDuplicateMaterial(cost)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Ionicons name="copy-outline" size={20} color={colors.text} />
</TouchableOpacity>
<TouchableOpacity
onPress={() => handleRemoveMaterial(cost.id)}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Ionicons
name="trash-outline"
size={20}
color={colors.error}
/>
</TouchableOpacity>
</View>
</View>
</View>
))}
{/* Add Button */}
<TouchableOpacity
style={[styles.addButton, themedStyles.addButton]}
onPress={handleAddMaterial}
activeOpacity={0.7}
>
<Ionicons name="add" size={20} color={colors.primary} />
<Text style={[styles.addButtonText, themedStyles.addButtonText]}>
{t("diary.addMaterialCost")}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginBottom: 20,
},
sectionTitle: {
fontSize: 16,
fontWeight: "700",
marginBottom: 16,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
costRow: {
marginBottom: 20,
paddingBottom: 16,
borderBottomWidth: 1,
borderBottomColor: "#E5E5E5",
},
formRow: {
flexDirection: "row",
alignItems: "flex-end",
gap: 12,
marginBottom: 12,
},
inputGroup: {
flex: 1,
},
typeGroup: {
flex: 1.5,
},
smallGroup: {
flex: 0.8,
},
mediumGroup: {
flex: 1,
},
fieldLabel: {
fontSize: 14,
fontWeight: "500",
marginBottom: 6,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
input: {
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
fontSize: 15,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
smallInput: {},
mediumInput: {},
dropdown: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
},
dropdownText: {
fontSize: 15,
flex: 1,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
readOnlyInput: {
justifyContent: "center",
opacity: 0.7,
},
readOnlyText: {
fontSize: 15,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
actionButtons: {
flexDirection: "row",
gap: 12,
alignItems: "center",
paddingBottom: 10,
},
addButton: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
paddingVertical: 12,
paddingHorizontal: 16,
borderWidth: 1.5,
borderRadius: 8,
borderStyle: "dashed",
marginTop: 4,
},
addButtonText: {
fontSize: 14,
fontWeight: "600",
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
// Modal styles
modalOverlay: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
modalContent: {
borderRadius: 12,
width: "80%",
maxHeight: "50%",
overflow: "hidden",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
},
option: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
},
optionText: {
fontSize: 16,
fontFamily: Platform.select({
ios: "System",
android: "Roboto",
default: "System",
}),
},
});