update netListTable, CreateOrUpdateHaulModal

This commit is contained in:
2025-11-10 16:11:02 +07:00
parent f3b0e7b7eb
commit c26de5aefc
2 changed files with 149 additions and 88 deletions

View File

@@ -96,7 +96,12 @@ const NetListTable: React.FC = () => {
{/* Cột Trạng thái */} {/* Cột Trạng thái */}
<View style={[styles.cell, styles.statusContainer]}> <View style={[styles.cell, styles.statusContainer]}>
<View style={styles.statusDot} /> <View
style={[
styles.statusDot,
{ backgroundColor: item.status ? "#2ecc71" : "#FFD600" },
]}
/>
<TouchableOpacity <TouchableOpacity
onPress={() => handleStatusPress(item.fishing_log_id)} onPress={() => handleStatusPress(item.fishing_log_id)}
> >
@@ -125,7 +130,12 @@ const NetListTable: React.FC = () => {
{/* Cột Trạng thái */} {/* Cột Trạng thái */}
<View style={[styles.cell, styles.statusContainer]}> <View style={[styles.cell, styles.statusContainer]}>
<View style={styles.statusDot} /> <View
style={[
styles.statusDot,
{ backgroundColor: item.status ? "#2ecc71" : "#FFD600" },
]}
/>
<TouchableOpacity <TouchableOpacity
onPress={() => handleStatusPress(item.fishing_log_id)} onPress={() => handleStatusPress(item.fishing_log_id)}
> >

View File

@@ -36,6 +36,14 @@ const UNITS_OPTIONS = UNITS.map((unit) => ({
value: unit.toString(), value: unit.toString(),
})); }));
const SIZE_UNITS = ["cm", "m"] as const;
type SizeUnit = (typeof SIZE_UNITS)[number];
const SIZE_UNITS_OPTIONS = SIZE_UNITS.map((unit) => ({
label: unit,
value: unit,
}));
// Zod schema cho 1 dòng cá // Zod schema cho 1 dòng cá
const fishItemSchema = z.object({ const fishItemSchema = z.object({
id: z.number().min(1, "Chọn loài cá"), id: z.number().min(1, "Chọn loài cá"),
@@ -47,6 +55,7 @@ const fishItemSchema = z.object({
.number({ invalid_type_error: "Kích thước phải là số" }) .number({ invalid_type_error: "Kích thước phải là số" })
.positive("Kích thước > 0") .positive("Kích thước > 0")
.optional(), .optional(),
sizeUnit: z.enum(SIZE_UNITS),
}); });
// Schema tổng: mảng các item // Schema tổng: mảng các item
@@ -60,6 +69,7 @@ const defaultItem = (): FormValues["fish"][number] => ({
quantity: 1, quantity: 1,
unit: "con", unit: "con",
size: undefined, size: undefined,
sizeUnit: "cm",
}); });
const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
@@ -210,6 +220,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
quantity: (h.catch_number as number) ?? 1, quantity: (h.catch_number as number) ?? 1,
unit: (h.catch_unit as Unit) ?? (defaultItem().unit as Unit), unit: (h.catch_unit as Unit) ?? (defaultItem().unit as Unit),
size: (h.fish_size as number) ?? undefined, size: (h.fish_size as number) ?? undefined,
sizeUnit: "cm" as SizeUnit,
})); }));
reset({ fish: mapped as any }); reset({ fish: mapped as any });
setIsCreateMode(false); setIsCreateMode(false);
@@ -225,9 +236,11 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
}, [isVisible, fishingLog?.info, reset]); }, [isVisible, fishingLog?.info, reset]);
const renderRow = (item: any, index: number) => { const renderRow = (item: any, index: number) => {
const isExpanded = expandedFishIndices.includes(index); const isExpanded = expandedFishIndices.includes(index);
// Give expanded card highest zIndex, others get decreasing zIndex based on position
const cardZIndex = isExpanded ? 1000 : 100 - index;
return ( return (
<View key={item._id} style={styles.fishCard}> <View key={item._id} style={[styles.fishCard, { zIndex: cardZIndex }]}>
{/* Delete + Chevron buttons - top right corner */} {/* Delete + Chevron buttons - top right corner */}
<View <View
style={{ style={{
@@ -315,13 +328,13 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
{/* Form - visible when expanded */} {/* Form - visible when expanded */}
{isExpanded && ( {isExpanded && (
<View style={{ paddingRight: 100 }}> <View style={{ paddingRight: 10 }}>
{/* Species dropdown */} {/* Species dropdown */}
<Controller <Controller
control={control} control={control}
name={`fish.${index}.id`} name={`fish.${index}.id`}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<View style={styles.fieldGroup}> <View style={[styles.fieldGroup, { marginTop: 20 }]}>
<Text style={styles.label}>Tên </Text> <Text style={styles.label}>Tên </Text>
<Select <Select
options={fishSpecies!.map((fish) => ({ options={fishSpecies!.map((fish) => ({
@@ -342,84 +355,117 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
)} )}
/> />
{/* Quantity */} {/* Số lượng & Đơn vị cùng hàng */}
<Controller <View style={{ flexDirection: "row", gap: 12 }}>
control={control} <View style={{ flex: 1 }}>
name={`fish.${index}.quantity`} <Controller
render={({ field: { value, onChange, onBlur } }) => ( control={control}
<View style={styles.fieldGroup}> name={`fish.${index}.quantity`}
<Text style={styles.label}>Số lượng</Text> render={({ field: { value, onChange, onBlur } }) => (
<TextInput <View style={styles.fieldGroup}>
keyboardType="numeric" <Text style={styles.label}>Số lượng</Text>
value={String(value ?? "")} <TextInput
onBlur={onBlur} keyboardType="numeric"
onChangeText={(t) => value={String(value ?? "")}
onChange(Number(t.replace(/,/g, ".")) || 0) onBlur={onBlur}
} onChangeText={(t) =>
style={[styles.input, !isEditing && styles.inputDisabled]} onChange(Number(t.replace(/,/g, ".")) || 0)
editable={isEditing} }
/> style={[
{errors.fish?.[index]?.quantity && ( styles.input,
<Text style={styles.errorText}> !isEditing && styles.inputDisabled,
{errors.fish[index]?.quantity?.message as string} ]}
</Text> editable={isEditing}
/>
{errors.fish?.[index]?.quantity && (
<Text style={styles.errorText}>
{errors.fish[index]?.quantity?.message as string}
</Text>
)}
</View>
)} )}
</View> />
)} </View>
/> <View style={{ flex: 1 }}>
<Controller
control={control}
name={`fish.${index}.unit`}
render={({ field: { value, onChange } }) => (
<View style={styles.fieldGroup}>
<Text style={styles.label}>Đơn vị</Text>
<Select
options={UNITS_OPTIONS.map((unit) => ({
label: unit.label,
value: unit.value,
}))}
value={value}
onChange={onChange}
placeholder="Chọn đơn vị"
disabled={!isEditing}
listStyle={{ maxHeight: 100 }}
/>
{errors.fish?.[index]?.unit && (
<Text style={styles.errorText}>
{errors.fish[index]?.unit?.message as string}
</Text>
)}
</View>
)}
/>
</View>
</View>
{/* Unit dropdown */} {/* Size (optional) + Unit dropdown */}
<Controller <View style={{ flexDirection: "row", gap: 12 }}>
control={control} <View style={{ flex: 1 }}>
name={`fish.${index}.unit`} <Controller
render={({ field: { value, onChange } }) => ( control={control}
<View style={styles.fieldGroup}> name={`fish.${index}.size`}
<Text style={styles.label}>Đơn vị</Text> render={({ field: { value, onChange, onBlur } }) => (
<Select <View style={styles.fieldGroup}>
options={UNITS_OPTIONS.map((unit) => ({ <Text style={styles.label}>Kích thước</Text>
label: unit.label, <TextInput
value: unit.value, keyboardType="numeric"
}))} value={value ? String(value) : ""}
value={value} onBlur={onBlur}
onChange={onChange} onChangeText={(t) =>
placeholder="Chọn đơn vị" onChange(t ? Number(t.replace(/,/g, ".")) : undefined)
disabled={!isEditing} }
listStyle={{ maxHeight: 100 }} style={[
/> styles.input,
{errors.fish?.[index]?.unit && ( !isEditing && styles.inputDisabled,
<Text style={styles.errorText}> ]}
{errors.fish[index]?.unit?.message as string} editable={isEditing}
</Text> />
{errors.fish?.[index]?.size && (
<Text style={styles.errorText}>
{errors.fish[index]?.size?.message as string}
</Text>
)}
</View>
)} )}
</View> />
)} </View>
/> <View style={{ flex: 1 }}>
<Controller
{/* Size (optional) */} control={control}
<Controller name={`fish.${index}.sizeUnit`}
control={control} render={({ field: { value, onChange } }) => (
name={`fish.${index}.size`} <View style={styles.fieldGroup}>
render={({ field: { value, onChange, onBlur } }) => ( <Text style={styles.label}>Đơn vị</Text>
<View style={styles.fieldGroup}> <Select
<Text style={styles.label}>Kích thước (cm) tùy chọn</Text> options={SIZE_UNITS_OPTIONS}
<TextInput value={value}
keyboardType="numeric" onChange={onChange}
value={value ? String(value) : ""} placeholder="Chọn đơn vị"
onBlur={onBlur} disabled={!isEditing}
onChangeText={(t) => listStyle={{ maxHeight: 80 }}
onChange(t ? Number(t.replace(/,/g, ".")) : undefined) />
} </View>
style={[styles.input, !isEditing && styles.inputDisabled]}
editable={isEditing}
/>
{errors.fish?.[index]?.size && (
<Text style={styles.errorText}>
{errors.fish[index]?.size?.message as string}
</Text>
)} )}
</View> />
)} </View>
/> </View>
</View> </View>
)} )}
</View> </View>
@@ -447,15 +493,20 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
<View style={styles.headerButtons}> <View style={styles.headerButtons}>
{isEditing ? ( {isEditing ? (
<> <>
<TouchableOpacity {!isCreateMode && (
onPress={() => { <TouchableOpacity
setIsEditing(false); onPress={() => {
reset(); // reset to previous values setIsEditing(false);
}} reset(); // reset to previous values
style={[styles.saveButton, { backgroundColor: "#6c757d" }]} }}
> style={[
<Text style={styles.saveButtonText}>Hủy</Text> styles.saveButton,
</TouchableOpacity> { backgroundColor: "#6c757d" },
]}
>
<Text style={styles.saveButtonText}>Hủy</Text>
</TouchableOpacity>
)}
<TouchableOpacity <TouchableOpacity
onPress={handleSubmit(onSubmit)} onPress={handleSubmit(onSubmit)}
style={styles.saveButton} style={styles.saveButton}