add en/vi language

This commit is contained in:
Tran Anh Tuan
2025-11-15 16:58:07 +07:00
parent 1a534eccb0
commit e725819c01
31 changed files with 1843 additions and 232 deletions

View File

@@ -1,4 +1,5 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useI18n } from "@/hooks/use-i18n";
import { useTrip } from "@/state/use-trip";
import React, { useRef, useState } from "react";
import { Animated, Text, TouchableOpacity, View } from "react-native";
@@ -13,6 +14,7 @@ const CrewListTable: React.FC = () => {
const [selectedCrew, setSelectedCrew] = useState<Model.TripCrews | null>(
null
);
const { t } = useI18n();
const { trip } = useTrip();
@@ -51,7 +53,7 @@ const CrewListTable: React.FC = () => {
onPress={handleToggle}
style={styles.headerRow}
>
<Text style={styles.title}>Danh sách thuyền viên</Text>
<Text style={styles.title}>{t("trip.crewList.title")}</Text>
{collapsed && (
<Text style={styles.totalCollapsed}>{tongThanhVien}</Text>
)}
@@ -75,10 +77,12 @@ const CrewListTable: React.FC = () => {
{/* Header */}
<View style={[styles.row, styles.tableHeader]}>
<View style={styles.cellWrapper}>
<Text style={[styles.cell, styles.headerText]}>Tên</Text>
<Text style={[styles.cell, styles.headerText]}>
{t("trip.crewList.nameHeader")}
</Text>
</View>
<Text style={[styles.cell, styles.right, styles.headerText]}>
Chức vụ
{t("trip.crewList.roleHeader")}
</Text>
</View>
@@ -99,7 +103,9 @@ const CrewListTable: React.FC = () => {
{/* Footer */}
<View style={[styles.row]}>
<Text style={[styles.cell, styles.footerText]}>Tổng cộng</Text>
<Text style={[styles.cell, styles.footerText]}>
{t("trip.crewList.totalLabel")}
</Text>
<Text style={[styles.cell, styles.footerTotal]}>{tongThanhVien}</Text>
</View>
</View>
@@ -109,10 +115,12 @@ const CrewListTable: React.FC = () => {
{/* Header */}
<View style={[styles.row, styles.tableHeader]}>
<View style={styles.cellWrapper}>
<Text style={[styles.cell, styles.headerText]}>Tên</Text>
<Text style={[styles.cell, styles.headerText]}>
{t("trip.crewList.nameHeader")}
</Text>
</View>
<Text style={[styles.cell, styles.right, styles.headerText]}>
Chức vụ
{t("trip.crewList.roleHeader")}
</Text>
</View>
@@ -133,7 +141,9 @@ const CrewListTable: React.FC = () => {
{/* Footer */}
<View style={[styles.row]}>
<Text style={[styles.cell, styles.footerText]}>Tổng cộng</Text>
<Text style={[styles.cell, styles.footerText]}>
{t("trip.crewList.totalLabel")}
</Text>
<Text style={[styles.cell, styles.footerTotal]}>{tongThanhVien}</Text>
</View>
</Animated.View>

View File

@@ -1,4 +1,5 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useI18n } from "@/hooks/use-i18n";
import { useTrip } from "@/state/use-trip";
import React, { useRef, useState } from "react";
import { Animated, Text, TouchableOpacity, View } from "react-native";
@@ -8,6 +9,7 @@ const FishingToolsTable: React.FC = () => {
const [collapsed, setCollapsed] = useState(true);
const [contentHeight, setContentHeight] = useState<number>(0);
const animatedHeight = useRef(new Animated.Value(0)).current;
const { t } = useI18n();
const { trip } = useTrip();
const data: Model.FishingGear[] = trip?.fishing_gears ?? [];
@@ -31,7 +33,7 @@ const FishingToolsTable: React.FC = () => {
onPress={handleToggle}
style={styles.headerRow}
>
<Text style={styles.title}>Danh sách ngư cụ</Text>
<Text style={styles.title}>{t("trip.fishingTools.title")}</Text>
{collapsed && <Text style={styles.totalCollapsed}>{tongSoLuong}</Text>}
<IconSymbol
name={collapsed ? "chevron.down" : "chevron.up"}
@@ -52,9 +54,11 @@ const FishingToolsTable: React.FC = () => {
>
{/* Table Header */}
<View style={[styles.row, styles.tableHeader]}>
<Text style={[styles.cell, styles.left, styles.headerText]}>Tên</Text>
<Text style={[styles.cell, styles.left, styles.headerText]}>
{t("trip.fishingTools.nameHeader")}
</Text>
<Text style={[styles.cell, styles.right, styles.headerText]}>
Số lượng
{t("trip.fishingTools.quantityHeader")}
</Text>
</View>
@@ -69,7 +73,7 @@ const FishingToolsTable: React.FC = () => {
{/* Footer */}
<View style={[styles.row]}>
<Text style={[styles.cell, styles.left, styles.footerText]}>
Tổng cộng
{t("trip.fishingTools.totalLabel")}
</Text>
<Text style={[styles.cell, styles.right, styles.footerTotal]}>
{tongSoLuong}
@@ -81,9 +85,11 @@ const FishingToolsTable: React.FC = () => {
<Animated.View style={{ height: animatedHeight, overflow: "hidden" }}>
{/* Table Header */}
<View style={[styles.row, styles.tableHeader]}>
<Text style={[styles.cell, styles.left, styles.headerText]}>Tên</Text>
<Text style={[styles.cell, styles.left, styles.headerText]}>
{t("trip.fishingTools.nameHeader")}
</Text>
<Text style={[styles.cell, styles.right, styles.headerText]}>
Số lượng
{t("trip.fishingTools.quantityHeader")}
</Text>
</View>
@@ -98,7 +104,7 @@ const FishingToolsTable: React.FC = () => {
{/* Footer */}
<View style={[styles.row]}>
<Text style={[styles.cell, styles.left, styles.footerText]}>
Tổng cộng
{t("trip.fishingTools.totalLabel")}
</Text>
<Text style={[styles.cell, styles.right, styles.footerTotal]}>
{tongSoLuong}

View File

@@ -1,4 +1,5 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useI18n } from "@/hooks/use-i18n";
import { useFishes } from "@/state/use-fish";
import { useTrip } from "@/state/use-trip";
import React, { useEffect, useRef, useState } from "react";
@@ -12,6 +13,7 @@ const NetListTable: React.FC = () => {
const animatedHeight = useRef(new Animated.Value(0)).current;
const [modalVisible, setModalVisible] = useState(false);
const [selectedNet, setSelectedNet] = useState<Model.FishingLog | null>(null);
const { t } = useI18n();
const { trip } = useTrip();
const { fishSpecies, getFishSpecies } = useFishes();
useEffect(() => {
@@ -49,7 +51,7 @@ const NetListTable: React.FC = () => {
onPress={handleToggle}
style={styles.headerRow}
>
<Text style={styles.title}>Danh sách mẻ lưới</Text>
<Text style={styles.title}>{t("trip.netList.title")}</Text>
{collapsed && (
<Text style={styles.totalCollapsed}>
{trip?.fishing_logs?.length}
@@ -84,15 +86,21 @@ const NetListTable: React.FC = () => {
>
{/* Header */}
<View style={[styles.row, styles.tableHeader]}>
<Text style={[styles.sttCell, styles.headerText]}>STT</Text>
<Text style={[styles.cell, styles.headerText]}>Trạng thái</Text>
<Text style={[styles.sttCell, styles.headerText]}>
{t("trip.netList.sttHeader")}
</Text>
<Text style={[styles.cell, styles.headerText]}>
{t("trip.netList.statusHeader")}
</Text>
</View>
{/* Body */}
{trip?.fishing_logs?.map((item, index) => (
<View key={item.fishing_log_id} style={styles.row}>
{/* Cột STT */}
<Text style={styles.sttCell}>Mẻ {index + 1}</Text>
<Text style={styles.sttCell}>
{t("trip.netList.haulPrefix")} {index + 1}
</Text>
{/* Cột Trạng thái */}
<View style={[styles.cell, styles.statusContainer]}>
@@ -106,7 +114,9 @@ const NetListTable: React.FC = () => {
onPress={() => handleStatusPress(item.fishing_log_id)}
>
<Text style={styles.statusText}>
{item.status ? "Đã hoàn thành" : "Chưa hoàn thành"}
{item.status
? t("trip.netList.completed")
: t("trip.netList.pending")}
</Text>
</TouchableOpacity>
</View>
@@ -118,15 +128,21 @@ const NetListTable: React.FC = () => {
<Animated.View style={{ height: animatedHeight, overflow: "hidden" }}>
{/* Header */}
<View style={[styles.row, styles.tableHeader]}>
<Text style={[styles.sttCell, styles.headerText]}>STT</Text>
<Text style={[styles.cell, styles.headerText]}>Trạng thái</Text>
<Text style={[styles.sttCell, styles.headerText]}>
{t("trip.netList.sttHeader")}
</Text>
<Text style={[styles.cell, styles.headerText]}>
{t("trip.netList.statusHeader")}
</Text>
</View>
{/* Body */}
{trip?.fishing_logs?.map((item, index) => (
<View key={item.fishing_log_id} style={styles.row}>
{/* Cột STT */}
<Text style={styles.sttCell}>Mẻ {index + 1}</Text>
<Text style={styles.sttCell}>
{t("trip.netList.haulPrefix")} {index + 1}
</Text>
{/* Cột Trạng thái */}
<View style={[styles.cell, styles.statusContainer]}>
@@ -140,7 +156,9 @@ const NetListTable: React.FC = () => {
onPress={() => handleStatusPress(item.fishing_log_id)}
>
<Text style={styles.statusText}>
{item.status ? "Đã hoàn thành" : "Chưa hoàn thành"}
{item.status
? t("trip.netList.completed")
: t("trip.netList.pending")}
</Text>
</TouchableOpacity>
</View>

View File

@@ -1,4 +1,5 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useI18n } from "@/hooks/use-i18n";
import { useTrip } from "@/state/use-trip";
import React, { useRef, useState } from "react";
import { Animated, Text, TouchableOpacity, View } from "react-native";
@@ -14,6 +15,7 @@ const TripCostTable: React.FC = () => {
const [contentHeight, setContentHeight] = useState<number>(0);
const [modalVisible, setModalVisible] = useState(false);
const animatedHeight = useRef(new Animated.Value(0)).current;
const { t } = useI18n();
const { trip } = useTrip();
@@ -50,7 +52,7 @@ const TripCostTable: React.FC = () => {
// marginBottom: 12,
}}
>
<Text style={styles.title}>Chi phí chuyến đi</Text>
<Text style={styles.title}>{t("trip.costTable.title")}</Text>
{collapsed && (
<Text
style={[
@@ -81,9 +83,11 @@ const TripCostTable: React.FC = () => {
{/* Header */}
<View style={[styles.row, styles.header]}>
<Text style={[styles.cell, styles.left, styles.headerText]}>
Loại
{t("trip.costTable.typeHeader")}
</Text>
<Text style={[styles.cell, styles.headerText]}>
{t("trip.costTable.totalCostHeader")}
</Text>
<Text style={[styles.cell, styles.headerText]}>Tổng chi phí</Text>
</View>
{/* Body */}
@@ -99,7 +103,7 @@ const TripCostTable: React.FC = () => {
{/* Footer */}
<View style={[styles.row]}>
<Text style={[styles.cell, styles.left, styles.footerText]}>
Tổng cộng
{t("trip.costTable.totalLabel")}
</Text>
<Text style={[styles.cell, styles.total]}>
{tongCong.toLocaleString()}
@@ -112,7 +116,9 @@ const TripCostTable: React.FC = () => {
style={styles.viewDetailButton}
onPress={handleViewDetail}
>
<Text style={styles.viewDetailText}>Xem chi tiết</Text>
<Text style={styles.viewDetailText}>
{t("trip.costTable.viewDetail")}
</Text>
</TouchableOpacity>
)}
</View>
@@ -121,9 +127,11 @@ const TripCostTable: React.FC = () => {
{/* Header */}
<View style={[styles.row, styles.header]}>
<Text style={[styles.cell, styles.left, styles.headerText]}>
Loại
{t("trip.costTable.typeHeader")}
</Text>
<Text style={[styles.cell, styles.headerText]}>
{t("trip.costTable.totalCostHeader")}
</Text>
<Text style={[styles.cell, styles.headerText]}>Tổng chi phí</Text>
</View>
{/* Body */}
@@ -139,7 +147,7 @@ const TripCostTable: React.FC = () => {
{/* Footer */}
<View style={[styles.row]}>
<Text style={[styles.cell, styles.left, styles.footerText]}>
Tổng cộng
{t("trip.costTable.totalLabel")}
</Text>
<Text style={[styles.cell, styles.total]}>
{tongCong.toLocaleString()}
@@ -152,7 +160,9 @@ const TripCostTable: React.FC = () => {
style={styles.viewDetailButton}
onPress={handleViewDetail}
>
<Text style={styles.viewDetailText}>Xem chi tiết</Text>
<Text style={styles.viewDetailText}>
{t("trip.costTable.viewDetail")}
</Text>
</TouchableOpacity>
)}
</Animated.View>

View File

@@ -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 </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 </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>

View File

@@ -1,4 +1,5 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useI18n } from "@/hooks/use-i18n";
import React from "react";
import { Modal, ScrollView, Text, TouchableOpacity, View } from "react-native";
import styles from "./style/CrewDetailModal.styles";
@@ -21,30 +22,46 @@ const CrewDetailModal: React.FC<CrewDetailModalProps> = ({
onClose,
crewData,
}) => {
const { t } = useI18n();
if (!crewData) return null;
const infoItems = [
{ label: "Mã định danh", value: crewData.Person.personal_id },
{ label: "Họ và tên", value: crewData.Person.name },
{ label: "Chức vụ", value: crewData.role },
{
label: "Ngày sinh",
label: t("trip.crewDetailModal.personalId"),
value: crewData.Person.personal_id,
},
{ label: t("trip.crewDetailModal.fullName"), value: crewData.Person.name },
{ label: t("trip.crewDetailModal.role"), value: crewData.role },
{
label: t("trip.crewDetailModal.birthDate"),
value: crewData.Person.birth_date
? new Date(crewData.Person.birth_date).toLocaleDateString()
: "Chưa cập nhật",
: t("trip.crewDetailModal.notUpdated"),
},
{ label: "Số điện thoại", value: crewData.Person.phone || "Chưa cập nhật" },
{ label: "Địa chỉ", value: crewData.Person.address || "Chưa cập nhật" },
{
label: "Ngày vào làm",
label: t("trip.crewDetailModal.phone"),
value: crewData.Person.phone || t("trip.crewDetailModal.notUpdated"),
},
{
label: t("trip.crewDetailModal.address"),
value: crewData.Person.address || t("trip.crewDetailModal.notUpdated"),
},
{
label: t("trip.crewDetailModal.joinedDate"),
value: crewData.joined_at
? new Date(crewData.joined_at).toLocaleDateString()
: "Chưa cập nhật",
: t("trip.crewDetailModal.notUpdated"),
},
{ label: "Ghi chú", value: crewData.note || "Chưa cập nhật" },
{
label: "Tình trạng",
value: crewData.left_at ? "Đã nghỉ" : "Đang làm việc",
label: t("trip.crewDetailModal.note"),
value: crewData.note || t("trip.crewDetailModal.notUpdated"),
},
{
label: t("trip.crewDetailModal.status"),
value: crewData.left_at
? t("trip.crewDetailModal.resigned")
: t("trip.crewDetailModal.working"),
},
];
@@ -58,7 +75,7 @@ const CrewDetailModal: React.FC<CrewDetailModalProps> = ({
<View style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}>Thông tin thuyền viên</Text>
<Text style={styles.title}>{t("trip.crewDetailModal.title")}</Text>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<View style={styles.closeIconButton}>
<IconSymbol name="xmark" size={28} color="#fff" />

View File

@@ -1,3 +1,4 @@
import { useI18n } from "@/hooks/use-i18n";
import React from "react";
import { Text, View } from "react-native";
import styles from "../../style/NetDetailModal.styles";
@@ -11,24 +12,31 @@ export const InfoSection: React.FC<InfoSectionProps> = ({
fishingLog,
stt,
}) => {
const { t } = useI18n();
if (!fishingLog) {
return null;
}
const infoItems = [
{ label: "Số thứ tự", value: `Mẻ ${stt}` },
{
label: "Trạng thái",
value: fishingLog.status === 1 ? "Đã hoàn thành" : "Chưa hoàn thành",
label: t("trip.infoSection.sttLabel"),
value: `${t("trip.infoSection.haulPrefix")} ${stt}`,
},
{
label: t("trip.infoSection.statusLabel"),
value:
fishingLog.status === 1
? t("trip.infoSection.statusCompleted")
: t("trip.infoSection.statusPending"),
isStatus: true,
},
{
label: "Thời gian bắt đầu",
label: t("trip.infoSection.startTimeLabel"),
value: fishingLog.start_at
? new Date(fishingLog.start_at).toLocaleString()
: "Chưa cập nhật",
: t("trip.infoSection.notUpdated"),
},
{
label: "Thời gian kết thúc",
label: t("trip.infoSection.endTimeLabel"),
value:
fishingLog.end_at.toString() !== "0001-01-01T00:00:00Z"
? new Date(fishingLog.end_at).toLocaleString()

View File

@@ -1,4 +1,5 @@
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useI18n } from "@/hooks/use-i18n";
import React, { useEffect, useState } from "react";
import {
KeyboardAvoidingView,
@@ -29,6 +30,7 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
onClose,
data,
}) => {
const { t } = useI18n();
const [isEditing, setIsEditing] = useState(false);
const [editableData, setEditableData] = useState<Model.TripCost[]>(data);
@@ -94,7 +96,7 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
<View style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}>Chi tiết chi phí chuyến đi</Text>
<Text style={styles.title}>{t("trip.costDetailModal.title")}</Text>
<View style={styles.headerButtons}>
{isEditing ? (
<>
@@ -102,13 +104,17 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
onPress={handleCancel}
style={styles.cancelButton}
>
<Text style={styles.cancelButtonText}>Hủy</Text>
<Text style={styles.cancelButtonText}>
{t("trip.costDetailModal.cancel")}
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={handleSave}
style={styles.saveButton}
>
<Text style={styles.saveButtonText}>Lưu</Text>
<Text style={styles.saveButtonText}>
{t("trip.costDetailModal.save")}
</Text>
</TouchableOpacity>
</>
) : (
@@ -140,13 +146,15 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
<View key={index} style={styles.itemCard}>
{/* Loại */}
<View style={styles.fieldGroup}>
<Text style={styles.label}>Loại chi phí</Text>
<Text style={styles.label}>
{t("trip.costDetailModal.costType")}
</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={item.type}
onChangeText={(value) => updateItem(index, "type", value)}
editable={isEditing}
placeholder="Nhập loại chi phí"
placeholder={t("trip.costDetailModal.enterCostType")}
/>
</View>
@@ -155,7 +163,9 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
<View
style={[styles.fieldGroup, { flex: 1, marginRight: 8 }]}
>
<Text style={styles.label}>Số lượng</Text>
<Text style={styles.label}>
{t("trip.costDetailModal.quantity")}
</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={String(item.amount ?? "")}
@@ -168,20 +178,24 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
/>
</View>
<View style={[styles.fieldGroup, { flex: 1, marginLeft: 8 }]}>
<Text style={styles.label}>Đơn vị</Text>
<Text style={styles.label}>
{t("trip.costDetailModal.unit")}
</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={item.unit}
onChangeText={(value) => updateItem(index, "unit", value)}
editable={isEditing}
placeholder="kg, lít..."
placeholder={t("trip.costDetailModal.placeholder")}
/>
</View>
</View>
{/* Chi phí/đơn vị */}
<View style={styles.fieldGroup}>
<Text style={styles.label}>Chi phí/đơn vị (VNĐ)</Text>
<Text style={styles.label}>
{t("trip.costDetailModal.costPerUnit")}
</Text>
<TextInput
style={[styles.input, !isEditing && styles.inputDisabled]}
value={String(item.cost_per_unit ?? "")}
@@ -196,10 +210,13 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
{/* Tổng chi phí */}
<View style={styles.fieldGroup}>
<Text style={styles.label}>Tổng chi phí</Text>
<Text style={styles.label}>
{t("trip.costDetailModal.totalCost")}
</Text>
<View style={styles.totalContainer}>
<Text style={styles.totalText}>
{item.total_cost.toLocaleString()} VNĐ
{item.total_cost.toLocaleString()}{" "}
{t("trip.costDetailModal.vnd")}
</Text>
</View>
</View>
@@ -208,9 +225,11 @@ const TripCostDetailModal: React.FC<TripCostDetailModalProps> = ({
{/* Footer Total */}
<View style={styles.footerTotal}>
<Text style={styles.footerLabel}>Tổng cộng</Text>
<Text style={styles.footerLabel}>
{t("trip.costDetailModal.total")}
</Text>
<Text style={styles.footerAmount}>
{tongCong.toLocaleString()} VNĐ
{tongCong.toLocaleString()} {t("trip.costDetailModal.vnd")}
</Text>
</View>
</ScrollView>