Compare commits
2 Commits
f3b0e7b7eb
...
1a534eccb0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a534eccb0 | ||
| c26de5aefc |
@@ -7,16 +7,7 @@ export default function Warning() {
|
|||||||
const [isShowModal, setIsShowModal] = useState(false);
|
const [isShowModal, setIsShowModal] = useState(false);
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<View style={styles.container}>
|
|
||||||
<CreateOrUpdateHaulModal
|
|
||||||
fishingLog={fishingLogData}
|
|
||||||
isVisible={isShowModal}
|
|
||||||
onClose={function (): void {
|
|
||||||
setIsShowModal(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button title="Thêm thu hoạch" onPress={() => setIsShowModal(true)} />
|
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -54,118 +45,3 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const fishingLogData:Model.FishingLog = {
|
|
||||||
fishing_log_id: "124b2701-a5d6-4eb0-ba3b-6789473c14a9",
|
|
||||||
trip_id: "d84caab6-ebb0-4cf7-abf9-31e5617d23b9",
|
|
||||||
start_at: "2025-11-07T10:50:01.693193764Z",
|
|
||||||
end_at: "2025-11-07T10:50:31.693027729Z",
|
|
||||||
start_lat: 11.59141,
|
|
||||||
start_lon: 109.0489,
|
|
||||||
haul_lat: 11.590274,
|
|
||||||
haul_lon: 109.049284,
|
|
||||||
status: 1,
|
|
||||||
weather_description: "Stormy",
|
|
||||||
info: [
|
|
||||||
{
|
|
||||||
fish_species_id: 8,
|
|
||||||
fish_name: "Cá hồng phớn",
|
|
||||||
catch_number: 1309,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 173,
|
|
||||||
fish_rarity: 3,
|
|
||||||
fish_condition: "Còn sống",
|
|
||||||
gear_usage: "Câu vàng",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 18,
|
|
||||||
fish_name: "Cá đuối quạt",
|
|
||||||
catch_number: 731,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 16,
|
|
||||||
fish_rarity: 4,
|
|
||||||
fish_condition: "Chết",
|
|
||||||
gear_usage: "Bẫy lưới",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 7,
|
|
||||||
fish_name: "Cá bơn vàng",
|
|
||||||
catch_number: 1224,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 12,
|
|
||||||
fish_rarity: 1,
|
|
||||||
fish_condition: "Chết",
|
|
||||||
gear_usage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 16,
|
|
||||||
fish_name: "Cá rồng biển",
|
|
||||||
catch_number: 838,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 164,
|
|
||||||
fish_rarity: 3,
|
|
||||||
fish_condition: "Chết",
|
|
||||||
gear_usage: "Lưới rê",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 9,
|
|
||||||
fish_name: "Cá hổ Napoleon",
|
|
||||||
catch_number: 1410,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 104,
|
|
||||||
fish_rarity: 4,
|
|
||||||
fish_condition: "Bị thương",
|
|
||||||
gear_usage: "Câu vàng",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 3,
|
|
||||||
fish_name: "Cá chim trắng",
|
|
||||||
catch_number: 1184,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 104,
|
|
||||||
fish_rarity: 2,
|
|
||||||
fish_condition: "Còn sống",
|
|
||||||
gear_usage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 5,
|
|
||||||
fish_name: "Cá mú đỏ",
|
|
||||||
catch_number: 987,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 171,
|
|
||||||
fish_rarity: 2,
|
|
||||||
fish_condition: "Bị thương",
|
|
||||||
gear_usage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 13,
|
|
||||||
fish_name: "Cá song đỏ",
|
|
||||||
catch_number: 1676,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 99,
|
|
||||||
fish_rarity: 2,
|
|
||||||
fish_condition: "Bị thương",
|
|
||||||
gear_usage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 11,
|
|
||||||
fish_name: "Cá ngừ đại dương",
|
|
||||||
catch_number: 462,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 11,
|
|
||||||
fish_rarity: 1,
|
|
||||||
fish_condition: "Bị thương",
|
|
||||||
gear_usage: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fish_species_id: 2,
|
|
||||||
fish_name: "Cá nục",
|
|
||||||
catch_number: 496,
|
|
||||||
catch_unit: "kg",
|
|
||||||
fish_size: 125,
|
|
||||||
fish_rarity: 1,
|
|
||||||
fish_condition: "Còn sống",
|
|
||||||
gear_usage: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
sync: true,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ export default function HomeScreen() {
|
|||||||
const [circleRadius, setCircleRadius] = useState(100);
|
const [circleRadius, setCircleRadius] = useState(100);
|
||||||
const [zoomLevel, setZoomLevel] = useState(10);
|
const [zoomLevel, setZoomLevel] = useState(10);
|
||||||
const [isFirstLoad, setIsFirstLoad] = useState(true);
|
const [isFirstLoad, setIsFirstLoad] = useState(true);
|
||||||
const [polylineCoordinates, setPolylineCoordinates] =
|
const [polylineCoordinates, setPolylineCoordinates] = useState<
|
||||||
useState<PolylineWithLabelProps | null>(null);
|
PolylineWithLabelProps[]
|
||||||
|
>([]);
|
||||||
const [polygonCoordinates, setPolygonCoordinates] = useState<
|
const [polygonCoordinates, setPolygonCoordinates] = useState<
|
||||||
PolygonWithLabelProps[]
|
PolygonWithLabelProps[]
|
||||||
>([]);
|
>([]);
|
||||||
@@ -69,7 +70,7 @@ export default function HomeScreen() {
|
|||||||
} else {
|
} else {
|
||||||
setGpsData(null);
|
setGpsData(null);
|
||||||
setPolygonCoordinates([]);
|
setPolygonCoordinates([]);
|
||||||
setPolylineCoordinates(null);
|
setPolylineCoordinates([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const queryAlarmData = (alarmData: Model.AlarmResponse) => {
|
const queryAlarmData = (alarmData: Model.AlarmResponse) => {
|
||||||
@@ -121,7 +122,7 @@ export default function HomeScreen() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPolylineCoordinates(null);
|
setPolylineCoordinates([]);
|
||||||
setPolygonCoordinates([]);
|
setPolygonCoordinates([]);
|
||||||
if (!entityData) return;
|
if (!entityData) return;
|
||||||
if (!banzoneData) return;
|
if (!banzoneData) return;
|
||||||
@@ -139,11 +140,14 @@ export default function HomeScreen() {
|
|||||||
}
|
}
|
||||||
// Nếu danh sách zone rỗng, clear tất cả
|
// Nếu danh sách zone rỗng, clear tất cả
|
||||||
if (zones.length === 0) {
|
if (zones.length === 0) {
|
||||||
setPolylineCoordinates(null);
|
setPolylineCoordinates([]);
|
||||||
setPolygonCoordinates([]);
|
setPolygonCoordinates([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let polylines: PolylineWithLabelProps[] = [];
|
||||||
|
let polygons: PolygonWithLabelProps[] = [];
|
||||||
|
|
||||||
for (const zone of zones) {
|
for (const zone of zones) {
|
||||||
// console.log("Zone Data: ", zone);
|
// console.log("Zone Data: ", zone);
|
||||||
const geom = banzoneData.find((b) => b.id === zone.zone_id);
|
const geom = banzoneData.find((b) => b.id === zone.zone_id);
|
||||||
@@ -161,7 +165,7 @@ export default function HomeScreen() {
|
|||||||
geom_lines || ""
|
geom_lines || ""
|
||||||
);
|
);
|
||||||
if (coordinates.length > 0) {
|
if (coordinates.length > 0) {
|
||||||
setPolylineCoordinates({
|
polylines.push({
|
||||||
coordinates: coordinates.map((coord) => ({
|
coordinates: coordinates.map((coord) => ({
|
||||||
latitude: coord[0],
|
latitude: coord[0],
|
||||||
longitude: coord[1],
|
longitude: coord[1],
|
||||||
@@ -169,25 +173,31 @@ export default function HomeScreen() {
|
|||||||
label: zone?.zone_name ?? "",
|
label: zone?.zone_name ?? "",
|
||||||
content: zone?.message ?? "",
|
content: zone?.message ?? "",
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Không tìm thấy polyline trong alarm");
|
||||||
}
|
}
|
||||||
} else if (geom_type === 1) {
|
} else if (geom_type === 1) {
|
||||||
// foundPolygon = true;
|
// foundPolygon = true;
|
||||||
const coordinates = convertWKTtoLatLngString(geom_poly || "");
|
const coordinates = convertWKTtoLatLngString(geom_poly || "");
|
||||||
if (coordinates.length > 0) {
|
if (coordinates.length > 0) {
|
||||||
// console.log("Polygon Coordinate: ", coordinates);
|
// console.log("Polygon Coordinate: ", coordinates);
|
||||||
setPolygonCoordinates(
|
const zonePolygons = coordinates.map((polygon) => ({
|
||||||
coordinates.map((polygon) => ({
|
|
||||||
coordinates: polygon.map((coord) => ({
|
coordinates: polygon.map((coord) => ({
|
||||||
latitude: coord[0],
|
latitude: coord[0],
|
||||||
longitude: coord[1],
|
longitude: coord[1],
|
||||||
})),
|
})),
|
||||||
label: zone?.zone_name ?? "",
|
label: zone?.zone_name ?? "",
|
||||||
content: zone?.message ?? "",
|
content: zone?.message ?? "",
|
||||||
}))
|
}));
|
||||||
);
|
polygons.push(...zonePolygons);
|
||||||
|
} else {
|
||||||
|
console.log("Không tìm thấy polygon trong alarm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPolylineCoordinates(polylines);
|
||||||
|
setPolygonCoordinates(polygons);
|
||||||
}
|
}
|
||||||
}, [banzoneData, entityData]);
|
}, [banzoneData, entityData]);
|
||||||
|
|
||||||
@@ -301,7 +311,7 @@ export default function HomeScreen() {
|
|||||||
// console.log(`Rendering circle ${index}:`, point);
|
// console.log(`Rendering circle ${index}:`, point);
|
||||||
return (
|
return (
|
||||||
<Circle
|
<Circle
|
||||||
key={index}
|
key={`circle-${index}`}
|
||||||
center={{
|
center={{
|
||||||
latitude: point.lat,
|
latitude: point.lat,
|
||||||
longitude: point.lon,
|
longitude: point.lon,
|
||||||
@@ -315,27 +325,27 @@ export default function HomeScreen() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{polylineCoordinates && (
|
{polylineCoordinates.length > 0 && (
|
||||||
|
<>
|
||||||
|
{polylineCoordinates.map((polyline, index) => (
|
||||||
<PolylineWithLabel
|
<PolylineWithLabel
|
||||||
key={`polyline-${gpsData?.lat || 0}-${gpsData?.lon || 0}`}
|
key={`polyline-${index}-${gpsData?.lat || 0}-${
|
||||||
coordinates={polylineCoordinates.coordinates}
|
gpsData?.lon || 0
|
||||||
label={polylineCoordinates.label}
|
}`}
|
||||||
content={polylineCoordinates.content}
|
coordinates={polyline.coordinates}
|
||||||
|
label={polyline.label}
|
||||||
|
content={polyline.content}
|
||||||
strokeColor="#FF5733"
|
strokeColor="#FF5733"
|
||||||
strokeWidth={4}
|
strokeWidth={4}
|
||||||
showDistance={false}
|
showDistance={false}
|
||||||
// zIndex={50}
|
// zIndex={50}
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{polygonCoordinates.length > 0 && (
|
{polygonCoordinates.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{polygonCoordinates.map((polygon, index) => {
|
{polygonCoordinates.map((polygon, index) => {
|
||||||
// Tạo key ổn định từ tọa độ đầu tiên của polygon
|
|
||||||
const polygonKey =
|
|
||||||
polygon.coordinates.length > 0
|
|
||||||
? `polygon-${polygon.coordinates[0].latitude}-${polygon.coordinates[0].longitude}-${index}`
|
|
||||||
: `polygon-${index}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PolygonWithLabel
|
<PolygonWithLabel
|
||||||
key={`polygon-${index}-${gpsData?.lat || 0}-${
|
key={`polygon-${index}-${gpsData?.lat || 0}-${
|
||||||
@@ -365,11 +375,6 @@ export default function HomeScreen() {
|
|||||||
latitude: gpsData.lat,
|
latitude: gpsData.lat,
|
||||||
longitude: gpsData.lon,
|
longitude: gpsData.lon,
|
||||||
}}
|
}}
|
||||||
title={
|
|
||||||
platform === IOS_PLATFORM
|
|
||||||
? "Tàu của mình - iOS"
|
|
||||||
: "Tàu của mình - Android"
|
|
||||||
}
|
|
||||||
zIndex={20}
|
zIndex={20}
|
||||||
anchor={
|
anchor={
|
||||||
platform === IOS_PLATFORM
|
platform === IOS_PLATFORM
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { StyleSheet } from "react-native";
|
|||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
import { ThemedView } from "@/components/themed-view";
|
import { ThemedView } from "@/components/themed-view";
|
||||||
import { api } from "@/config";
|
import { api } from "@/config";
|
||||||
import { TOKEN } from "@/constants";
|
import { DOMAIN, TOKEN } from "@/constants";
|
||||||
import { removeStorageItem } from "@/utils/storage";
|
import { removeStorageItem } from "@/utils/storage";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -37,9 +37,10 @@ export default function SettingScreen() {
|
|||||||
<ThemedText type="title">Settings</ThemedText>
|
<ThemedText type="title">Settings</ThemedText>
|
||||||
<ThemedView
|
<ThemedView
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
onTouchEnd={() => {
|
onTouchEnd={async () => {
|
||||||
removeStorageItem(TOKEN);
|
await removeStorageItem(TOKEN);
|
||||||
router.replace("/auth/login");
|
await removeStorageItem(DOMAIN);
|
||||||
|
router.navigate("/auth/login");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ThemedText type="defaultSemiBold">Đăng xuất</ThemedText>
|
<ThemedText type="defaultSemiBold">Đăng xuất</ThemedText>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
import ScanQRCode from "@/components/ScanQRCode";
|
||||||
import { ThemedText } from "@/components/themed-text";
|
import { ThemedText } from "@/components/themed-text";
|
||||||
import { ThemedView } from "@/components/themed-view";
|
import { ThemedView } from "@/components/themed-view";
|
||||||
import { TOKEN } from "@/constants";
|
import { DOMAIN, TOKEN } from "@/constants";
|
||||||
import { login } from "@/controller/AuthController";
|
import { login } from "@/controller/AuthController";
|
||||||
import { showErrorToast } from "@/services/toast_service";
|
import { showErrorToast, showWarningToast } from "@/services/toast_service";
|
||||||
import {
|
import {
|
||||||
getStorageItem,
|
getStorageItem,
|
||||||
removeStorageItem,
|
removeStorageItem,
|
||||||
setStorageItem,
|
setStorageItem,
|
||||||
} from "@/utils/storage";
|
} from "@/utils/storage";
|
||||||
import { parseJwtToken } from "@/utils/token";
|
import { parseJwtToken } from "@/utils/token";
|
||||||
|
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -29,22 +31,30 @@ export default function LoginScreen() {
|
|||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [isShowingQRScanner, setIsShowingQRScanner] = useState(false);
|
||||||
|
|
||||||
const checkLogin = useCallback(async () => {
|
const checkLogin = useCallback(async () => {
|
||||||
const token = await getStorageItem(TOKEN);
|
const token = await getStorageItem(TOKEN);
|
||||||
console.log("Token:", token);
|
const domain = await getStorageItem(DOMAIN);
|
||||||
|
// console.log("Token:", token);
|
||||||
|
// removeStorageItem(DOMAIN);
|
||||||
|
console.log("Domain:", domain);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!domain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const parsed = parseJwtToken(token);
|
const parsed = parseJwtToken(token);
|
||||||
console.log("Parse Token: ", parsed);
|
// console.log("Parse Token: ", parsed);
|
||||||
|
|
||||||
const { exp } = parsed;
|
const { exp } = parsed;
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const oneHour = 60 * 60;
|
const oneHour = 60 * 60;
|
||||||
if (exp - now < oneHour) {
|
if (exp - now < oneHour) {
|
||||||
await removeStorageItem(TOKEN);
|
await removeStorageItem(TOKEN);
|
||||||
|
await removeStorageItem(DOMAIN);
|
||||||
} else {
|
} else {
|
||||||
router.replace("/(tabs)");
|
router.replace("/(tabs)");
|
||||||
}
|
}
|
||||||
@@ -54,9 +64,41 @@ export default function LoginScreen() {
|
|||||||
checkLogin();
|
checkLogin();
|
||||||
}, [checkLogin]);
|
}, [checkLogin]);
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleQRCodeScanned = async (data: string) => {
|
||||||
|
console.log("QR Code Scanned Data:", data);
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(data);
|
||||||
|
if (parsed.username && parsed.password) {
|
||||||
|
// update UI fields
|
||||||
|
setUsername(parsed.username);
|
||||||
|
setPassword(parsed.password);
|
||||||
|
console.log("Domain: ", parsed.device_ip);
|
||||||
|
|
||||||
|
// close scanner so user sees the filled form
|
||||||
|
await setStorageItem(DOMAIN, parsed.device_ip);
|
||||||
|
|
||||||
|
// // call login directly with scanned credentials to avoid waiting for state to update
|
||||||
|
await handleLogin({
|
||||||
|
username: parsed.username,
|
||||||
|
password: parsed.password,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showWarningToast("Mã QR không hợp lệ");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showWarningToast("Mã QR không hợp lệ");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogin = async (creds?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}) => {
|
||||||
|
const user = creds?.username ?? username;
|
||||||
|
const pass = creds?.password ?? password;
|
||||||
|
|
||||||
// Validate input
|
// Validate input
|
||||||
if (!username.trim() || !password.trim()) {
|
if (!user?.trim() || !pass?.trim()) {
|
||||||
showErrorToast("Vui lòng nhập tài khoản và mật khẩu");
|
showErrorToast("Vui lòng nhập tài khoản và mật khẩu");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,8 +106,8 @@ export default function LoginScreen() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const body: Model.LoginRequestBody = {
|
const body: Model.LoginRequestBody = {
|
||||||
username: username,
|
username: user,
|
||||||
password: password,
|
password: pass,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await login(body);
|
const response = await login(body);
|
||||||
@@ -131,25 +173,56 @@ export default function LoginScreen() {
|
|||||||
{/* Password Input */}
|
{/* Password Input */}
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<ThemedText style={styles.label}>Mật khẩu</ThemedText>
|
<ThemedText style={styles.label}>Mật khẩu</ThemedText>
|
||||||
|
<View className="relative">
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
placeholder="Nhập mật khẩu"
|
placeholder="Nhập mật khẩu"
|
||||||
placeholderTextColor="#999"
|
placeholderTextColor="#999"
|
||||||
value={password}
|
value={password}
|
||||||
onChangeText={setPassword}
|
onChangeText={setPassword}
|
||||||
secureTextEntry
|
secureTextEntry={!showPassword}
|
||||||
editable={!loading}
|
editable={!loading}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
/>
|
/>
|
||||||
|
{/* Position absolute with top:0 and bottom:0 and justifyContent:center
|
||||||
|
ensures the icon remains vertically centered inside the input */}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setShowPassword(!showPassword)}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 12,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: 4,
|
||||||
|
}}
|
||||||
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name={showPassword ? "eye-off" : "eye"}
|
||||||
|
size={22}
|
||||||
|
color="#666"
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Login Button */}
|
{/* Login Button (3/4) + QR Scan (1/4) */}
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.loginButton,
|
styles.loginButton,
|
||||||
loading && styles.loginButtonDisabled,
|
loading && styles.loginButtonDisabled,
|
||||||
|
{ flex: 5, marginRight: 12, marginTop: 0 },
|
||||||
]}
|
]}
|
||||||
onPress={handleLogin}
|
onPress={() => handleLogin()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -159,6 +232,29 @@ export default function LoginScreen() {
|
|||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: 10,
|
||||||
|
marginTop: 0,
|
||||||
|
borderColor: "#ddd",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
onPress={() => setIsShowingQRScanner(true)}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<MaterialIcons
|
||||||
|
name="qr-code-scanner"
|
||||||
|
size={28}
|
||||||
|
color="#007AFF"
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Footer text */}
|
{/* Footer text */}
|
||||||
<View style={styles.footerContainer}>
|
<View style={styles.footerContainer}>
|
||||||
<ThemedText style={styles.footerText}>
|
<ThemedText style={styles.footerText}>
|
||||||
@@ -176,6 +272,11 @@ export default function LoginScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
<ScanQRCode
|
||||||
|
visible={isShowingQRScanner}
|
||||||
|
onClose={() => setIsShowingQRScanner(false)}
|
||||||
|
onScanned={handleQRCodeScanned}
|
||||||
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export default function ScanQRCode({
|
|||||||
const [permission, requestPermission] = useCameraPermissions();
|
const [permission, requestPermission] = useCameraPermissions();
|
||||||
const [scanned, setScanned] = useState(false);
|
const [scanned, setScanned] = useState(false);
|
||||||
const cameraRef = useRef(null);
|
const cameraRef = useRef(null);
|
||||||
|
// Dùng ref để chặn quét nhiều lần trong cùng một frame/event loop
|
||||||
|
const hasScannedRef = useRef(false);
|
||||||
|
|
||||||
// Request camera permission when component mounts or when visible changes to true
|
// Request camera permission when component mounts or when visible changes to true
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,6 +40,13 @@ export default function ScanQRCode({
|
|||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
|
// Mỗi khi reset scanned state thì reset luôn ref guard
|
||||||
|
useEffect(() => {
|
||||||
|
if (!scanned) {
|
||||||
|
hasScannedRef.current = false;
|
||||||
|
}
|
||||||
|
}, [scanned]);
|
||||||
|
|
||||||
const handleBarCodeScanned = ({
|
const handleBarCodeScanned = ({
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
@@ -45,11 +54,12 @@ export default function ScanQRCode({
|
|||||||
type: string;
|
type: string;
|
||||||
data: string;
|
data: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!scanned) {
|
// Nếu đã scan rồi, bỏ qua
|
||||||
|
if (hasScannedRef.current || scanned) return;
|
||||||
|
hasScannedRef.current = true;
|
||||||
setScanned(true);
|
setScanned(true);
|
||||||
onScanned(data);
|
onScanned(data);
|
||||||
onClose();
|
onClose();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!permission) {
|
if (!permission) {
|
||||||
@@ -102,7 +112,8 @@ export default function ScanQRCode({
|
|||||||
<CameraView
|
<CameraView
|
||||||
ref={cameraRef}
|
ref={cameraRef}
|
||||||
style={styles.camera}
|
style={styles.camera}
|
||||||
onBarcodeScanned={handleBarCodeScanned}
|
// Chỉ gắn handler khi chưa scan để ngắt lắng nghe ngay lập tức sau khi quét thành công
|
||||||
|
onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
|
||||||
barcodeScannerSettings={{
|
barcodeScannerSettings={{
|
||||||
barcodeTypes: ["qr"],
|
barcodeTypes: ["qr"],
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { showToastError } from "@/config";
|
|
||||||
import {
|
import {
|
||||||
queryDeleteSos,
|
queryDeleteSos,
|
||||||
queryGetSos,
|
queryGetSos,
|
||||||
querySendSosMessage,
|
querySendSosMessage,
|
||||||
} from "@/controller/DeviceController";
|
} from "@/controller/DeviceController";
|
||||||
|
import { showErrorToast } from "@/services/toast_service";
|
||||||
import { sosMessage } from "@/utils/sosUtils";
|
import { sosMessage } from "@/utils/sosUtils";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { MaterialIcons } from "@expo/vector-icons";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -108,7 +108,7 @@ const SosButton = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error when send sos: ", error);
|
console.error("Error when send sos: ", error);
|
||||||
showToastError("Không thể gửi tín hiệu SOS", "Lỗi");
|
showErrorToast("Không thể gửi tín hiệu SOS");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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 cá</Text>
|
<Text style={styles.label}>Tên cá</Text>
|
||||||
<Select
|
<Select
|
||||||
options={fishSpecies!.map((fish) => ({
|
options={fishSpecies!.map((fish) => ({
|
||||||
@@ -342,7 +355,9 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Quantity */}
|
{/* Số lượng & Đơn vị cùng hàng */}
|
||||||
|
<View style={{ flexDirection: "row", gap: 12 }}>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`fish.${index}.quantity`}
|
name={`fish.${index}.quantity`}
|
||||||
@@ -356,7 +371,10 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
|||||||
onChangeText={(t) =>
|
onChangeText={(t) =>
|
||||||
onChange(Number(t.replace(/,/g, ".")) || 0)
|
onChange(Number(t.replace(/,/g, ".")) || 0)
|
||||||
}
|
}
|
||||||
style={[styles.input, !isEditing && styles.inputDisabled]}
|
style={[
|
||||||
|
styles.input,
|
||||||
|
!isEditing && styles.inputDisabled,
|
||||||
|
]}
|
||||||
editable={isEditing}
|
editable={isEditing}
|
||||||
/>
|
/>
|
||||||
{errors.fish?.[index]?.quantity && (
|
{errors.fish?.[index]?.quantity && (
|
||||||
@@ -367,8 +385,8 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
{/* Unit dropdown */}
|
<View style={{ flex: 1 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`fish.${index}.unit`}
|
name={`fish.${index}.unit`}
|
||||||
@@ -394,14 +412,18 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Size (optional) */}
|
{/* Size (optional) + Unit dropdown */}
|
||||||
|
<View style={{ flexDirection: "row", gap: 12 }}>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`fish.${index}.size`}
|
name={`fish.${index}.size`}
|
||||||
render={({ field: { value, onChange, onBlur } }) => (
|
render={({ field: { value, onChange, onBlur } }) => (
|
||||||
<View style={styles.fieldGroup}>
|
<View style={styles.fieldGroup}>
|
||||||
<Text style={styles.label}>Kích thước (cm) — tùy chọn</Text>
|
<Text style={styles.label}>Kích thước</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
value={value ? String(value) : ""}
|
value={value ? String(value) : ""}
|
||||||
@@ -409,7 +431,10 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
|||||||
onChangeText={(t) =>
|
onChangeText={(t) =>
|
||||||
onChange(t ? Number(t.replace(/,/g, ".")) : undefined)
|
onChange(t ? Number(t.replace(/,/g, ".")) : undefined)
|
||||||
}
|
}
|
||||||
style={[styles.input, !isEditing && styles.inputDisabled]}
|
style={[
|
||||||
|
styles.input,
|
||||||
|
!isEditing && styles.inputDisabled,
|
||||||
|
]}
|
||||||
editable={isEditing}
|
editable={isEditing}
|
||||||
/>
|
/>
|
||||||
{errors.fish?.[index]?.size && (
|
{errors.fish?.[index]?.size && (
|
||||||
@@ -421,6 +446,27 @@ const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`fish.${index}.sizeUnit`}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<View style={styles.fieldGroup}>
|
||||||
|
<Text style={styles.label}>Đơn vị</Text>
|
||||||
|
<Select
|
||||||
|
options={SIZE_UNITS_OPTIONS}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder="Chọn đơn vị"
|
||||||
|
disabled={!isEditing}
|
||||||
|
listStyle={{ maxHeight: 80 }}
|
||||||
|
/>
|
||||||
|
</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 ? (
|
||||||
<>
|
<>
|
||||||
|
{!isCreateMode && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
reset(); // reset to previous values
|
reset(); // reset to previous values
|
||||||
}}
|
}}
|
||||||
style={[styles.saveButton, { backgroundColor: "#6c757d" }]}
|
style={[
|
||||||
|
styles.saveButton,
|
||||||
|
{ backgroundColor: "#6c757d" },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.saveButtonText}>Hủy</Text>
|
<Text style={styles.saveButtonText}>Hủy</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={handleSubmit(onSubmit)}
|
onPress={handleSubmit(onSubmit)}
|
||||||
style={styles.saveButton}
|
style={styles.saveButton}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const InfoSection: React.FC<InfoSectionProps> = ({
|
|||||||
{
|
{
|
||||||
label: "Thời gian kết thúc",
|
label: "Thời gian kết thúc",
|
||||||
value:
|
value:
|
||||||
fishingLog.end_at !== "0001-01-01T00:00:00Z"
|
fishingLog.end_at.toString() !== "0001-01-01T00:00:00Z"
|
||||||
? new Date(fishingLog.end_at).toLocaleString()
|
? new Date(fishingLog.end_at).toLocaleString()
|
||||||
: "-",
|
: "-",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TOKEN } from "@/constants";
|
import { DOMAIN, TOKEN } from "@/constants";
|
||||||
import { removeStorageItem } from "@/utils/storage";
|
import { removeStorageItem } from "@/utils/storage";
|
||||||
import { Router } from "expo-router";
|
import { Router } from "expo-router";
|
||||||
|
|
||||||
@@ -17,7 +17,12 @@ export const setRouterInstance = (router: Router) => {
|
|||||||
export const handle401 = () => {
|
export const handle401 = () => {
|
||||||
if (routerInstance) {
|
if (routerInstance) {
|
||||||
removeStorageItem(TOKEN);
|
removeStorageItem(TOKEN);
|
||||||
(routerInstance as any).replace("/auth/login");
|
removeStorageItem(DOMAIN);
|
||||||
|
// Cancel all pending requests to prevent further API calls
|
||||||
|
if (typeof window !== "undefined" && (window as any).axiosAbortController) {
|
||||||
|
(window as any).axiosAbortController.abort();
|
||||||
|
}
|
||||||
|
routerInstance.navigate("/auth/login");
|
||||||
} else {
|
} else {
|
||||||
console.warn("Router instance not set, cannot redirect to login");
|
console.warn("Router instance not set, cannot redirect to login");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TOKEN } from "@/constants";
|
import { DOMAIN, TOKEN } from "@/constants";
|
||||||
|
import { showErrorToast } from "@/services/toast_service";
|
||||||
import { getStorageItem } from "@/utils/storage";
|
import { getStorageItem } from "@/utils/storage";
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
import { handle401 } from "./auth";
|
import { handle401 } from "./auth";
|
||||||
import { showToastError } from "./toast";
|
|
||||||
|
|
||||||
const codeMessage = {
|
const codeMessage = {
|
||||||
200: "The server successfully returned the requested data。",
|
200: "The server successfully returned the requested data。",
|
||||||
@@ -38,9 +38,15 @@ api.interceptors.request.use(
|
|||||||
async (config) => {
|
async (config) => {
|
||||||
// Thêm auth token nếu có
|
// Thêm auth token nếu có
|
||||||
const token = await getStorageItem(TOKEN);
|
const token = await getStorageItem(TOKEN);
|
||||||
|
const domain = await getStorageItem(DOMAIN);
|
||||||
|
if (domain) {
|
||||||
|
config.baseURL = `http://${domain}`;
|
||||||
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `${token}`;
|
config.headers.Authorization = `${token}`;
|
||||||
}
|
}
|
||||||
|
// console.log("Domain Request: ", config.baseURL);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -57,7 +63,9 @@ api.interceptors.response.use(
|
|||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
const networkErrorMsg =
|
const networkErrorMsg =
|
||||||
error.message || "Network error - please check connection";
|
error.message || "Network error - please check connection";
|
||||||
showToastError("Lỗi kết nối", networkErrorMsg);
|
showErrorToast("Lỗi kết nối");
|
||||||
|
console.error("Response Network Error: ", networkErrorMsg);
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +78,7 @@ api.interceptors.response.use(
|
|||||||
statusText ||
|
statusText ||
|
||||||
"Unknown error";
|
"Unknown error";
|
||||||
|
|
||||||
showToastError(`Lỗi ${status}`, errMsg);
|
showErrorToast(`Lỗi ${status}: ${errMsg}`);
|
||||||
|
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
handle401();
|
handle401();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const TOKEN = "token";
|
export const TOKEN = "token";
|
||||||
export const BASE_URL = "https://sgw-device.gms.vn";
|
export const DOMAIN = "domain";
|
||||||
export const MAP_TRACKPOINTS_ID = "ship-trackpoints";
|
export const MAP_TRACKPOINTS_ID = "ship-trackpoints";
|
||||||
export const MAP_POLYLINE_BAN = "ban-polyline";
|
export const MAP_POLYLINE_BAN = "ban-polyline";
|
||||||
export const MAP_POLYGON_BAN = "ban-polygon";
|
export const MAP_POLYGON_BAN = "ban-polygon";
|
||||||
|
|||||||
Reference in New Issue
Block a user