diff --git a/sgw-device-v1.1.zip b/sgw-device-v1.1.zip
new file mode 100644
index 0000000..3c5c7c5
Binary files /dev/null and b/sgw-device-v1.1.zip differ
diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx
index f6223f7..2d43f3f 100644
--- a/src/components/Footer/Footer.tsx
+++ b/src/components/Footer/Footer.tsx
@@ -1,7 +1,15 @@
import { DefaultFooter } from '@ant-design/pro-components';
-
+import './style.less';
const Footer = () => {
- return ;
+ return (
+
+ );
};
export default Footer;
diff --git a/src/components/Footer/style.less b/src/components/Footer/style.less
new file mode 100644
index 0000000..925951f
--- /dev/null
+++ b/src/components/Footer/style.less
@@ -0,0 +1,3 @@
+.ant-pro-global-footer-copyright {
+ color: white !important; /* hoặc mã màu bạn muốn */
+}
diff --git a/src/constants/enums.ts b/src/constants/enums.ts
index 3d0d90e..ce0b962 100644
--- a/src/constants/enums.ts
+++ b/src/constants/enums.ts
@@ -6,3 +6,7 @@ export enum STATUS {
UPDATE_FISHING_LOG_SUCCESS = 'UPDATE_FISHING_LOG_SUCCESS',
UPDATE_FISHING_LOG_FAIL = 'UPDATE_FISHING_LOG_FAIL',
}
+
+export enum ENTITY_TYPE_ENUM {
+ SOS_WARNING = '50:15',
+}
diff --git a/src/models/getSos.ts b/src/models/getSos.ts
index 21a2a72..209c74a 100644
--- a/src/models/getSos.ts
+++ b/src/models/getSos.ts
@@ -10,7 +10,7 @@ export default function useGetGpsModel() {
const res = await getGPS(); // đổi URL cho phù hợp
console.log('GPS Data fetched:', res);
- setGpsData(res || []);
+ setGpsData(res);
} catch (err) {
console.error('Fetch gps data failed', err);
} finally {
diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx
index 34d60ee..cf9863b 100644
--- a/src/pages/Auth/index.tsx
+++ b/src/pages/Auth/index.tsx
@@ -1,3 +1,4 @@
+import Footer from '@/components/Footer/Footer';
import { ROUTE_HOME } from '@/constants';
import { login } from '@/services/controller/AuthController';
import { parseJwt } from '@/utils/jwtTokenUtils';
@@ -127,6 +128,17 @@ const LoginPage = () => {
/>
>
+
+
+
);
};
diff --git a/src/pages/Home/components/BaseMap.tsx b/src/pages/Home/components/BaseMap.tsx
index b097269..e814dec 100644
--- a/src/pages/Home/components/BaseMap.tsx
+++ b/src/pages/Home/components/BaseMap.tsx
@@ -7,16 +7,21 @@ import {
} from '@/utils/mapUtils';
import { Feature, Map, View } from 'ol';
import { Coordinate } from 'ol/coordinate';
+import { easeOut } from 'ol/easing';
+import { EventsKey } from 'ol/events';
import { createEmpty, extend, Extent, isEmpty } from 'ol/extent';
import { LineString, MultiPolygon, Point, Polygon } from 'ol/geom';
import BaseLayer from 'ol/layer/Base';
import VectorLayer from 'ol/layer/Vector';
+import { unByKey } from 'ol/Observable';
import { fromLonLat } from 'ol/proj';
+import { getVectorContext } from 'ol/render';
+import RenderEvent from 'ol/render/Event';
import VectorSource from 'ol/source/Vector';
+import { Circle as CircleStyle, Style } from 'ol/style';
import Fill from 'ol/style/Fill';
import Icon from 'ol/style/Icon';
import Stroke from 'ol/style/Stroke';
-import Style from 'ol/style/Style';
import Text from 'ol/style/Text';
interface MapManagerConfig {
@@ -25,6 +30,12 @@ interface MapManagerConfig {
onFeaturesClick?: (features: any[]) => void;
onError?: (error: string[]) => void;
}
+interface AnimationConfig {
+ duration?: number;
+ maxRadius?: number;
+ strokeColor?: string;
+ strokeWidthBase?: number;
+}
interface StyleConfig {
icon?: string; // URL của icon
@@ -66,6 +77,7 @@ class MapManager {
private errors: string[];
private layers: BaseLayer[]; // Assuming layers is defined elsewhere
private isInitialized: boolean; // Thêm thuộc tính để theo dõi trạng thái khởi tạo
+ private eventKey: EventsKey | undefined;
constructor(
mapRef: React.RefObject,
@@ -83,11 +95,12 @@ class MapManager {
this.errors = [];
this.layers = []; // Initialize layers (adjust based on actual usage)
this.isInitialized = false; // Khởi tạo là false
+ this.eventKey = undefined; // Khởi tạo eventKey
}
async getListLayers(): Promise<[]> {
const resp: [] = await getAllLayer();
- console.log('resp', resp);
+ // console.log('resp', resp);
return resp;
}
@@ -328,8 +341,10 @@ class MapManager {
// Lưu feature
this.features.push(feature);
this.featureLayer?.getSource()?.addFeature(feature);
-
- console.log('Point added successfully:', { coord, data, styleConfig });
+ if (styleConfig.animate) {
+ this.animatedMarker(feature, styleConfig.animationConfig);
+ }
+ // console.log('Point added successfully:', { coord, data, styleConfig });
return feature;
} catch (error: any) {
@@ -340,6 +355,74 @@ class MapManager {
return null;
}
}
+ animatedMarker = (
+ feature: Feature,
+ config: AnimationConfig = {},
+ ): void => {
+ const {
+ duration = 3000,
+ maxRadius = 20,
+ strokeColor = 'rgba(255, 0, 0, 0.8)',
+ strokeWidthBase = 0.25,
+ } = config;
+ console.log('Starting animatedMarker with config:', config);
+
+ const flashGeom = feature.getGeometry()?.clone() as Point | undefined;
+ if (!flashGeom || !this.featureLayer) {
+ console.error('Invalid geometry or featureLayer for animation');
+ return;
+ }
+
+ // Tạo 2 "pha" sóng, mỗi sóng bắt đầu cách nhau duration/2
+ const waveCount = 1;
+ const waveOffsets = Array.from(
+ { length: waveCount },
+ (_, i) => (duration / waveCount) * i,
+ );
+ const start = Date.now();
+
+ const listenerKey: EventsKey = this.featureLayer.on(
+ 'postrender',
+ (event: RenderEvent) => {
+ const frameState = event.frameState;
+ if (!frameState) return;
+
+ const elapsedTotal = frameState.time - start;
+ if (elapsedTotal >= duration * 3) {
+ // chạy 2 chu kỳ rồi dừng (bạn có thể bỏ điều kiện này để chạy mãi)
+ unByKey(listenerKey);
+ return;
+ }
+
+ const vectorContext = getVectorContext(event);
+
+ waveOffsets.forEach((offset) => {
+ const elapsed =
+ (frameState.time - start - offset + duration) % duration;
+ const elapsedRatio = elapsed / duration;
+
+ const radius = easeOut(elapsedRatio) * (maxRadius - 5) + 5;
+ const opacity = easeOut(1 - elapsedRatio);
+
+ const style = new Style({
+ image: new CircleStyle({
+ radius,
+ stroke: new Stroke({
+ color: strokeColor.replace('0.8', opacity.toString()),
+ width: strokeWidthBase + opacity,
+ }),
+ }),
+ });
+
+ vectorContext.setStyle(style);
+ vectorContext.drawGeometry(flashGeom);
+ });
+
+ this.map!.render();
+ },
+ );
+ this.eventKey = listenerKey;
+ };
addPolygon(
coords: Coordinate[][],
@@ -510,12 +593,12 @@ class MapManager {
if (this.featureLayer && this.isInitialized) {
this.features.push(feature);
this.featureLayer.getSource()?.addFeature(feature);
- console.log(
- 'LineString added successfully at',
- new Date().toLocaleTimeString(),
- ':',
- { coords, data, styleConfig },
- );
+ // console.log(
+ // 'LineString added successfully at',
+ // new Date().toLocaleTimeString(),
+ // ':',
+ // { coords, data, styleConfig },
+ // );
// Đảm bảo bản đồ được render lại
this.map?.render();
return feature;
@@ -540,10 +623,10 @@ class MapManager {
styleConfig: Record = {},
zoom: number,
) => {
- console.log(
- `createPolygonStyle called with zoom: ${zoom}, styleConfig:`,
- styleConfig,
- ); // Debug
+ // console.log(
+ // `createPolygonStyle called with zoom: ${zoom}, styleConfig:`,
+ // styleConfig,
+ // ); // Debug
const textContent = `Tên: ${data?.name ?? 'Chưa có'}\n\n Loại: ${
data?.type ?? 0
}\n\nThông tin: ${data?.description ?? 'Chưa có'}`;
@@ -581,7 +664,7 @@ class MapManager {
text: textStyle,
});
- console.log(`Polygon style created:`, style); // Debug
+ // console.log(`Polygon style created:`, style); // Debug
return [style];
};
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index c2eeaf1..c54d0f1 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -4,6 +4,7 @@ import {
MAP_TRACKPOINTS_ID,
ROUTE_TRIP,
} from '@/constants';
+import { ENTITY_TYPE_ENUM } from '@/constants/enums';
import {
queryAlarms,
queryEntities,
@@ -29,6 +30,21 @@ import ShipInfo from './components/ShipInfo';
import SosButton from './components/SosButton';
import VietNamMap, { MapManager } from './components/VietNamMap';
+// // Define missing types locally for now
+// interface AlarmData {
+// level: number;
+// }
+
+// interface EntityData {
+// id: string;
+// valueString: string;
+// }
+
+// interface TrackPoint {
+// lat: number;
+// lon: number;
+// }
+
export interface GpsData {
lat: number;
lon: number;
@@ -78,9 +94,9 @@ const HomePage: React.FC = () => {
getGPSData();
const interval = setInterval(() => {
getGPSData();
- getEntitiesData();
- getShipTrackPoints();
- getEntityData();
+ getEntitiesData(mapManagerRef.current, gpsData!);
+ fetchAndAddTrackPoints(mapManagerRef.current);
+ fetchAndProcessEntities(mapManagerRef.current);
}, 5000);
return () => {
clearInterval(interval);
@@ -91,61 +107,108 @@ const HomePage: React.FC = () => {
useEffect(() => {
const timer = setTimeout(() => {
// getGPSData();
- getEntitiesData();
- getShipTrackPoints();
- getEntityData();
+ getEntitiesData(mapManagerRef.current, gpsData!);
+ fetchAndAddTrackPoints(mapManagerRef.current);
+ fetchAndProcessEntities(mapManagerRef.current);
}, 1000); // delay 1s
return () => clearTimeout(timer);
}, [gpsData]);
- const getEntitiesData = async () => {
+ // Helper function to add features to the map
+ const addFeatureToMap = (
+ mapManager: MapManager,
+ gpsData: GpsData,
+ alarm: API.AlarmResponse,
+ ) => {
+ console.log(
+ 'Adding feature to map with GPS data:',
+ gpsData,
+ 'and alarm:',
+ alarm,
+ );
+ const isSos = alarm?.alarms.find(
+ (a) => a.id === ENTITY_TYPE_ENUM.SOS_WARNING,
+ );
+ console.log('Is SOS Alarm Present:', isSos);
+
+ if (mapManager?.featureLayer && gpsData) {
+ mapManager.featureLayer.getSource()?.clear();
+ mapManager.addPoint(
+ [gpsData.lon, gpsData.lat],
+ { bearing: gpsData.h || 0 },
+ {
+ icon: getShipIcon(alarm.level || 0, gpsData?.fishing || false),
+ scale: 0.1,
+ animate: isSos ? true : false,
+ },
+ );
+ }
+ };
+
+ const fetchAndProcessEntities = async (mapManager: MapManager) => {
try {
- const alarm = await queryAlarms();
- // console.log('Fetched alarm data:', alarm);
- // console.log('GPS Data:', gpsData);
+ const entities: API.TransformedEntity[] = await queryEntities();
+ console.log('Fetched entities:', entities.length);
- if (mapManagerRef.current?.featureLayer && gpsData) {
- // Clear trước
- mapManagerRef.current.featureLayer.getSource()?.clear();
+ for (const entity of entities) {
+ if (entity.id === '50:2' && entity.valueString !== '[]') {
+ const banzones = await queryBanzones();
+ const zones: any[] = JSON.parse(entity.valueString);
- try {
- // Tạo feature mới
- const feature = mapManagerRef.current.addPoint(
- [gpsData.lon, gpsData.lat],
- {
- bearing: gpsData.h || 0,
- },
- {
- icon: getShipIcon(alarm.level || 0, gpsData?.fishing || false),
- scale: 0.1,
- },
- );
- } catch (parseError) {
- console.error(
- `Error parsing valueString for entity: ${parseError}`,
- parseError,
- );
+ zones.forEach((zone: any) => {
+ const geom = banzones.find((b) => b.id === zone.zone_id);
+ if (geom) {
+ const { geom_type, geom_lines, geom_poly } = geom.geom || {};
+ if (geom_type === 2) {
+ const coordinates = convertWKTLineStringToLatLngArray(
+ geom_lines || '',
+ );
+ if (coordinates.length > 0) {
+ mapManager.addLineString(
+ coordinates,
+ {
+ id: MAP_POLYLINE_BAN,
+ text: `${geom.name} - ${zone.message}`,
+ },
+ { strokeColor: 'red', strokeWidth: 4 },
+ );
+ }
+ } else if (geom_type === 1) {
+ const coordinates = convertWKTtoLatLngString(geom_poly || '');
+ if (coordinates.length > 0) {
+ mapManager.addPolygon(
+ coordinates,
+ {
+ id: MAP_POLYGON_BAN,
+ name: geom.name,
+ type: getBanzoneNameByType(geom.type || 4),
+ description: zone.message,
+ },
+ {
+ strokeColor: '#FCC61D',
+ strokeWidth: 2,
+ fillColor: '#FCC61D',
+ },
+ );
+ }
+ }
+ }
+ });
}
}
} catch (error) {
console.error('Error fetching entities:', error);
}
};
- const getShipTrackPoints = async () => {
+
+ const fetchAndAddTrackPoints = async (mapManager: MapManager) => {
try {
- const trackpoints = await queryShipTrackPoints();
- // console.log('Fetched ship track points:', trackpoints.length);
+ const trackpoints: API.ShipTrackPoint[] = await queryShipTrackPoints();
if (trackpoints.length > 0) {
- mapManagerRef.current?.addLineString(
+ mapManager.addLineString(
trackpoints.map((point) => [point.lon, point.lat]),
- {
- id: MAP_TRACKPOINTS_ID,
- // You can add more properties here if needed
- },
- {
- strokeColor: 'blue',
- strokeWidth: 3,
- },
+ { id: MAP_TRACKPOINTS_ID },
+ { strokeColor: 'blue', strokeWidth: 3 },
);
}
} catch (error) {
@@ -153,70 +216,10 @@ const HomePage: React.FC = () => {
}
};
- const getEntityData = async () => {
+ const getEntitiesData = async (mapManager: MapManager, gpsData: GpsData) => {
try {
- const entities = await queryEntities();
- console.log('Fetched entities:', entities.length);
- if (entities.length > 0) {
- entities.forEach(async (entity) => {
- if (entity.id == '50:2') {
- const banzones = await queryBanzones();
- console.log('Fetched banzones:', banzones.length);
- if (entity.valueString != '[]') {
- const zones = JSON.parse(entity.valueString);
- console.log('Parsed zones:', zones);
- for (const zone of zones) {
- const geom = banzones.find((b) => b.id === zone.zone_id);
- if (geom) {
- if (geom.geom?.geom_type === 2) {
- // Polyline
- const coordinates = convertWKTLineStringToLatLngArray(
- geom?.geom?.geom_lines || '',
- );
- if (coordinates.length > 0) {
- mapManagerRef.current.addLineString(
- coordinates,
- {
- id: MAP_POLYLINE_BAN,
- text: `${geom.name} - ${zone.message}`,
- },
- {
- strokeColor: 'red',
- strokeWidth: 4,
- },
- );
- }
- } else if (geom.geom?.geom_type === 1) {
- const coordinates = convertWKTtoLatLngString(
- geom?.geom?.geom_poly || '',
- );
- if (coordinates.length > 0) {
- mapManagerRef.current.addPolygon(
- coordinates,
- {
- id: MAP_POLYGON_BAN,
- name: geom.name,
- type: getBanzoneNameByType(geom.type || 4),
- description: zone.message,
- },
- {
- strokeColor: 'yellow',
- strokeWidth: 2,
- fillColor: 'rgba(255, 255, 0, 0.3)', // Màu vàng với độ trong suốt
- },
- );
- }
- }
- } else {
- console.log('No geometry found for zone:', zone.id);
- }
- }
- } else {
- console.log('Zone rỗng');
- }
- }
- });
- }
+ const alarm: API.AlarmResponse = await queryAlarms();
+ addFeatureToMap(mapManager, gpsData, alarm);
} catch (error) {
console.error('Error fetching entities:', error);
}
@@ -268,7 +271,12 @@ const HomePage: React.FC = () => {
{
if (value) {
- getEntitiesData();
+ // Ensure null check for gpsData in all usages
+ if (gpsData) {
+ getEntitiesData(mapManagerRef.current, gpsData);
+ } else {
+ console.warn('GPS data is null, skipping getEntitiesData call');
+ }
}
}}
/>