add en/vi language
This commit is contained in:
@@ -2,6 +2,7 @@ import Select from "@/components/Select";
|
||||
import { IconSymbol } from "@/components/ui/icon-symbol";
|
||||
import { queryGpsData } from "@/controller/DeviceController";
|
||||
import { queryUpdateFishingLogs } from "@/controller/TripController";
|
||||
import { useI18n } from "@/hooks/use-i18n";
|
||||
import { showErrorToast, showSuccessToast } from "@/services/toast_service";
|
||||
import { useFishes } from "@/state/use-fish";
|
||||
import { useTrip } from "@/state/use-trip";
|
||||
@@ -46,21 +47,16 @@ const SIZE_UNITS_OPTIONS = SIZE_UNITS.map((unit) => ({
|
||||
|
||||
// Zod schema cho 1 dòng cá
|
||||
const fishItemSchema = z.object({
|
||||
id: z.number().min(1, "Chọn loài cá"),
|
||||
quantity: z
|
||||
.number({ invalid_type_error: "Số lượng phải là số" })
|
||||
.positive("Số lượng > 0"),
|
||||
unit: z.enum(UNITS, { required_error: "Chọn đơn vị" }),
|
||||
size: z
|
||||
.number({ invalid_type_error: "Kích thước phải là số" })
|
||||
.positive("Kích thước > 0")
|
||||
.optional(),
|
||||
id: z.number().min(1, ""),
|
||||
quantity: z.number({ invalid_type_error: "" }).positive(""),
|
||||
unit: z.enum(UNITS, { required_error: "" }),
|
||||
size: z.number({ invalid_type_error: "" }).positive("").optional(),
|
||||
sizeUnit: z.enum(SIZE_UNITS),
|
||||
});
|
||||
|
||||
// Schema tổng: mảng các item
|
||||
const formSchema = z.object({
|
||||
fish: z.array(fishItemSchema).min(1, "Thêm ít nhất 1 loài cá"),
|
||||
fish: z.array(fishItemSchema).min(1, ""),
|
||||
});
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
@@ -78,6 +74,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
fishingLog,
|
||||
fishingLogIndex,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const [isCreateMode, setIsCreateMode] = React.useState(!fishingLog?.info);
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
const [expandedFishIndices, setExpandedFishIndices] = React.useState<
|
||||
@@ -112,7 +109,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
const onSubmit = async (values: FormValues) => {
|
||||
// Ensure species list is available so we can populate name/rarity
|
||||
if (!fishSpecies || fishSpecies.length === 0) {
|
||||
showErrorToast("Danh sách loài cá chưa sẵn sàng");
|
||||
showErrorToast(t("trip.createHaulModal.fishListNotReady"));
|
||||
return;
|
||||
}
|
||||
// Helper to map form rows -> API info entries (single place)
|
||||
@@ -134,7 +131,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
try {
|
||||
const gpsResp = await queryGpsData();
|
||||
if (!gpsResp.data) {
|
||||
showErrorToast("Không thể lấy dữ liệu GPS hiện tại");
|
||||
showErrorToast(t("trip.createHaulModal.gpsError"));
|
||||
return;
|
||||
}
|
||||
const gpsData = gpsResp.data;
|
||||
@@ -177,21 +174,21 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
if (resp?.status === 200) {
|
||||
showSuccessToast(
|
||||
fishingLog?.fishing_log_id == null
|
||||
? "Thêm mẻ cá thành công"
|
||||
: "Cập nhật mẻ cá thành công"
|
||||
? t("trip.createHaulModal.addSuccess")
|
||||
: t("trip.createHaulModal.updateSuccess")
|
||||
);
|
||||
getTrip();
|
||||
onClose();
|
||||
} else {
|
||||
showErrorToast(
|
||||
fishingLog?.fishing_log_id == null
|
||||
? "Thêm mẻ cá thất bại"
|
||||
: "Cập nhật mẻ cá thất bại"
|
||||
? t("trip.createHaulModal.addError")
|
||||
: t("trip.createHaulModal.updateError")
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("onSubmit error:", err);
|
||||
showErrorToast("Có lỗi xảy ra khi lưu mẻ cá");
|
||||
showErrorToast(t("trip.createHaulModal.validationError"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -315,7 +312,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
return (
|
||||
<View style={styles.fishCardHeaderContent}>
|
||||
<Text style={styles.fishCardTitle}>
|
||||
{fishName || "Chọn loài cá"}:
|
||||
{fishName || t("trip.createHaulModal.selectFish")}:
|
||||
</Text>
|
||||
<Text style={styles.fishCardSubtitle}>
|
||||
{fishName ? `${quantity} ${unit}` : "---"}
|
||||
@@ -335,7 +332,9 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
name={`fish.${index}.id`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<View style={[styles.fieldGroup, { marginTop: 20 }]}>
|
||||
<Text style={styles.label}>Tên cá</Text>
|
||||
<Text style={styles.label}>
|
||||
{t("trip.createHaulModal.fishName")}
|
||||
</Text>
|
||||
<Select
|
||||
options={fishSpecies!.map((fish) => ({
|
||||
label: fish.name,
|
||||
@@ -343,12 +342,12 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
}))}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Chọn loài cá"
|
||||
placeholder={t("trip.createHaulModal.selectFish")}
|
||||
disabled={!isEditing}
|
||||
/>
|
||||
{errors.fish?.[index]?.id && (
|
||||
<Text style={styles.errorText}>
|
||||
{errors.fish[index]?.id?.message as string}
|
||||
{t("trip.createHaulModal.selectFish")}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
@@ -363,7 +362,9 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
name={`fish.${index}.quantity`}
|
||||
render={({ field: { value, onChange, onBlur } }) => (
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={styles.label}>Số lượng</Text>
|
||||
<Text style={styles.label}>
|
||||
{t("trip.createHaulModal.quantity")}
|
||||
</Text>
|
||||
<TextInput
|
||||
keyboardType="numeric"
|
||||
value={String(value ?? "")}
|
||||
@@ -379,7 +380,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
/>
|
||||
{errors.fish?.[index]?.quantity && (
|
||||
<Text style={styles.errorText}>
|
||||
{errors.fish[index]?.quantity?.message as string}
|
||||
{t("trip.createHaulModal.quantity")}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
@@ -392,7 +393,9 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
name={`fish.${index}.unit`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={styles.label}>Đơn vị</Text>
|
||||
<Text style={styles.label}>
|
||||
{t("trip.createHaulModal.unit")}
|
||||
</Text>
|
||||
<Select
|
||||
options={UNITS_OPTIONS.map((unit) => ({
|
||||
label: unit.label,
|
||||
@@ -400,13 +403,13 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
}))}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Chọn đơn vị"
|
||||
placeholder={t("trip.createHaulModal.unit")}
|
||||
disabled={!isEditing}
|
||||
listStyle={{ maxHeight: 100 }}
|
||||
/>
|
||||
{errors.fish?.[index]?.unit && (
|
||||
<Text style={styles.errorText}>
|
||||
{errors.fish[index]?.unit?.message as string}
|
||||
{t("trip.createHaulModal.unit")}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
@@ -423,7 +426,10 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
name={`fish.${index}.size`}
|
||||
render={({ field: { value, onChange, onBlur } }) => (
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={styles.label}>Kích thước</Text>
|
||||
<Text style={styles.label}>
|
||||
{t("trip.createHaulModal.size")} (
|
||||
{t("trip.createHaulModal.optional")})
|
||||
</Text>
|
||||
<TextInput
|
||||
keyboardType="numeric"
|
||||
value={value ? String(value) : ""}
|
||||
@@ -439,7 +445,7 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
/>
|
||||
{errors.fish?.[index]?.size && (
|
||||
<Text style={styles.errorText}>
|
||||
{errors.fish[index]?.size?.message as string}
|
||||
{t("trip.createHaulModal.size")}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
@@ -452,12 +458,14 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
name={`fish.${index}.sizeUnit`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<View style={styles.fieldGroup}>
|
||||
<Text style={styles.label}>Đơn vị</Text>
|
||||
<Text style={styles.label}>
|
||||
{t("trip.createHaulModal.unit")}
|
||||
</Text>
|
||||
<Select
|
||||
options={SIZE_UNITS_OPTIONS}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Chọn đơn vị"
|
||||
placeholder={t("trip.createHaulModal.unit")}
|
||||
disabled={!isEditing}
|
||||
listStyle={{ maxHeight: 80 }}
|
||||
/>
|
||||
@@ -488,7 +496,9 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>
|
||||
{isCreateMode ? "Thêm mẻ cá" : "Chỉnh sửa mẻ cá"}
|
||||
{isCreateMode
|
||||
? t("trip.createHaulModal.addFish")
|
||||
: t("trip.createHaulModal.edit")}
|
||||
</Text>
|
||||
<View style={styles.headerButtons}>
|
||||
{isEditing ? (
|
||||
@@ -504,14 +514,18 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
{ backgroundColor: "#6c757d" },
|
||||
]}
|
||||
>
|
||||
<Text style={styles.saveButtonText}>Hủy</Text>
|
||||
<Text style={styles.saveButtonText}>
|
||||
{t("trip.createHaulModal.cancel")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
style={styles.saveButton}
|
||||
>
|
||||
<Text style={styles.saveButtonText}>Lưu</Text>
|
||||
<Text style={styles.saveButtonText}>
|
||||
{t("trip.createHaulModal.save")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
) : (
|
||||
@@ -520,7 +534,9 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
onPress={() => setIsEditing(true)}
|
||||
style={[styles.saveButton, { backgroundColor: "#17a2b8" }]}
|
||||
>
|
||||
<Text style={styles.saveButtonText}>Sửa</Text>
|
||||
<Text style={styles.saveButtonText}>
|
||||
{t("trip.createHaulModal.edit")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)}
|
||||
@@ -546,14 +562,16 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
||||
onPress={() => append(defaultItem())}
|
||||
style={styles.addButton}
|
||||
>
|
||||
<Text style={styles.addButtonText}>+ Thêm loài cá</Text>
|
||||
<Text style={styles.addButtonText}>
|
||||
+ {t("trip.createHaulModal.addFish")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{errors.fish && (
|
||||
<Text style={styles.errorText}>
|
||||
{(errors.fish as any)?.message}
|
||||
{t("trip.createHaulModal.validationError")}
|
||||
</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
Reference in New Issue
Block a user