Files
FE-DEVICE-SGW/cauTrucDuAn.md

37 KiB

📊 PHÂN TÍCH CHI TIẾT DỰ ÁN FE-DEVICE-SGW

🎯 TỔNG QUAN DỰ ÁN

Đây là một hệ thống Giám sát tàu cá (Ship Monitoring System) được phát triển cho Mobifone, sử dụng UmiJS (Ant Design Pro) framework với React, TypeScript, và TailwindCSS.


🏗️ CẤU TRÚC TỔNG THỂ

FE-DEVICE-SGW/
├── config/          # Cấu hình ứng dụng
├── src/             # Mã nguồn chính
├── public/          # Tài nguyên tĩnh
├── mock/            # Dữ liệu giả lập
└── node_modules/    # Thư viện

📂 PHÂN TÍCH CHI TIẾT TỪNG FILE & THƯ MỤC

1. CẤU HÌNH GỐC (Root Configuration)

.umirc.ts - File cấu hình chính của UmiJS

// Định nghĩa môi trường: dev/test/prod
const { REACT_APP_ENV = 'dev' } = process.env;

// Cấu hình các plugin:
- antd: Sử dụng Ant Design
- access: Quản  quyền truy cập
- model: State management (tương tự Redux)
- initialState: Trạng thái khởi tạo
- request: HTTP request handler
- locale: Đa ngôn ngữ (mặc định: vi-VN)

// Layout:
- Logo, title: "2025 Sản phẩm của Mobifone v1.0"
- Theme: light, layout mix
- Icon font từ Alibaba

// Routes (Routing):
1. /login  Trang đăng nhập
2. /  Redirect về /map
3. /map  Trang giám sát ( icon map)
4. /trip  Trang chuyến đi ( icon specification)

Ý nghĩa: File này là "trái tim" của ứng dụng, quy định cách ứng dụng hoạt động.


package.json - Quản lý dependencies

Dependencies chính:
- @umijs/max: Framework chính
- antd: UI component library
- ol (OpenLayers): Thư viện bản đồ
- moment: Xử  thời gian
- classnames: Quản  className động

Scripts:
- dev: Chạy môi trường development
- build: Build production
- format: Format code với Prettier

tailwind.config.js - Cấu hình TailwindCSS

// Quét các file .tsx trong:
- src/pages/**/*.tsx
- src/components/**/*.tsx
- src/layouts/**/*.tsx
// để tạo CSS utility classes

2. FOLDER CONFIG/ - CẤU HÌNH ỨNG DỤNG

config/proxy.ts - Cấu hình proxy API

dev:   /api → http://192.168.30.102:81  // Môi trường phát triển
test:  /test → https://test-sgw-device.gms.vn
prod:  /test → https://prod-sgw-device.gms.vn

Vai trò: Chuyển hướng API calls để tránh CORS, dễ switch giữa các môi trường.


config/Request.ts - Cấu hình HTTP Request

// ErrorShowType: Định nghĩa cách hiển thị lỗi
enum ErrorShowType {
  SILENT = 0,        // Không hiển thị
  WARN_MESSAGE = 1,  // Cảnh báo
  ERROR_MESSAGE = 2, // Lỗi
  NOTIFICATION = 3,  // Thông báo
  REDIRECT = 9,      // Chuyển hướng
}

// Request config:
- timeout: 20s
- errorHandler: Xử  lỗi HTTP (401  redirect login)
- requestInterceptors: Thêm Authorization header với token

// Luồng request:
1. Lấy token từ localStorage
2. Gắn token vào header
3. Gửi request
4. Nhận response
5. Xử  lỗi nếu 

Ý nghĩa: Tập trung xử lý tất cả HTTP requests, xử lý lỗi thống nhất.


3. FOLDER SRC/ - MÃ NGUỒN CHÍNH

src/app.tsx - Bootstrap Application

// getInitialState(): Chạy TRƯỚC KHI app render
export async function getInitialState() {
  1. Lấy token từ localStorage
  2. Nếu không  token  redirect login
  3. Parse JWT token
  4. Kiểm tra token còn hạn không (> 1 giờ)
  5. Nếu hết hạn  xóa token, redirect login
  6. Return { currentUser, exp }
}

// layout: Cấu hình layout của app
export const layout: RunTimeLayoutConfig = (initialState) => {
  return {
    logo: logo image
    fixedHeader: true
    navTheme: 'light'
    avatarProps: {
      // Hiển thị dropdown với nút "Đăng xuất"
      onClick:  removeToken()  redirect login
    }
    onPageChange: Kiểm tra initialState mỗi lần chuyển trang
    unAccessible: Component hiển thị khi không  quyền truy cập
  }
}

// request: Export cấu hình request
export const request = handleRequestConfig;

Luồng khởi động ứng dụng:

1. User truy cập URL
2. UmiJS gọi getInitialState()
3. Kiểm tra authentication
4. Nếu OK: render app với layout
5. Nếu FAIL: redirect /login

src/access.ts - Quản lý quyền truy cập

// Nhận initialState từ app.tsx
export default (initialState: API.UserInfo) => {
  const canSeeAdmin = !!(
    initialState && initialState.name !== 'dontHaveAccess'
  );
  return {
    canSeeAdmin, // Có thể dùng trong routes để giới hạn quyền
  };
};

4. FOLDER SRC/CONSTANTS/ - HẰNG SỐ

constants/index.ts

// Token key
export const TOKEN = 'token';

// Routes
export const ROUTE_LOGIN = '/login';
export const ROUTE_HOME = '/map';
export const ROUTE_TRIP = '/trip';

// API Endpoints
export const API_PATH_LOGIN = '/api/agent/login';
export const API_PATH_ENTITIES = '/api/io/entities';
export const API_GET_GPS = '/api/sgw/gps';
export const API_GET_TRIP = '/api/sgw/trip';
export const API_SOS = '/api/sgw/sos';
// ... các API khác

constants/enums.ts

// Trạng thái xử lý fishing log
export enum STATUS {
  CREATE_FISHING_LOG_SUCCESS,
  CREATE_FISHING_LOG_FAIL,
  START_TRIP_SUCCESS,
  // ...
}

// Loại cảnh báo
export enum ENTITY_TYPE_ENUM {
  SOS_WARNING = '50:15',
}

5. FOLDER SRC/UTILS/ - TIỆN ÍCH

utils/localStorageUtils.ts

export function getToken(): string {
  return localStorage.getItem(TOKEN) || '';
}

export function setToken(token: string) {
  localStorage.setItem(TOKEN, token);
}

export function removeToken() {
  localStorage.removeItem(TOKEN);
}

utils/jwtTokenUtils.ts

  • parseJwt(token): Giải mã JWT token
  • Trích xuất thông tin: sub (subject/username), exp (expiry time)

utils/geomUtils.ts

  • convertWKTLineStringToLatLngArray(): Chuyển đổi WKT LineString sang tọa độ
  • convertWKTtoLatLngString(): Chuyển đổi WKT Polygon sang tọa độ
  • getBanzoneNameByType(): Lấy tên vùng cấm theo type

utils/mapUtils.ts

  • Các hàm tiện ích cho bản đồ

utils/sosUtil.ts

  • Xử lý logic SOS

utils/format.ts

  • Format dữ liệu hiển thị

6. FOLDER SRC/MODELS/ - STATE MANAGEMENT

UmiJS sử dụng Model (giống Redux) để quản lý state toàn cục.

models/getSos.ts - Model quản lý GPS data

export default function useGetGpsModel() {
  const [gpsData, setGpsData] = useState<API.GPSResonse | null>(null);
  const [loading, setLoading] = useState(false);

  const getGPSData = useCallback(async () => {
    setLoading(true);
    const res = await getGPS();
    setGpsData(res);
    setLoading(false);
  }, []);

  return { gpsData, loading, getGPSData };
}

Cách sử dụng:

// Trong component:
const { gpsData, getGPSData } = useModel('getSos');

models/getTrip.ts - Model quản lý Trip data

export default function useGetTripModel() {
  const [data, setData] = useState<API.Trip | null>(null);
  const [loading, setLoading] = useState(false);

  const getApi = useCallback(async () => {
    const res = await getTrip();
    setData(res);
  }, []);

  return { data, loading, getApi };
}

models/getAlarm.ts - Model quản lý Alarm data

  • Quản lý state danh sách cảnh báo

models/global.ts - Model chung

const useUser = () => {
  const [name, setName] = useState<string>(DEFAULT_NAME);
  return { name, setName };
};

7. FOLDER SRC/SERVICES/ - GỌI API

services/controller/AuthController.ts

export async function login(body: API.LoginRequestBody) {
  return request<API.LoginResponse>(API_PATH_LOGIN, {
    method: 'POST',
    data: body,
  });
}

Luồng login:

1. User nhập username/password
2. Gọi login(body)
3. Backend trả về { token }
4. Lưu token vào localStorage
5. Redirect về /map

services/controller/DeviceController.ts

// Lấy danh sách entities (thiết bị/tàu)
export async function queryEntities(): Promise<API.TransformedEntity[]> {
  const rawList = await request<API.EntityResponse[]>(API_PATH_ENTITIES);
  return rawList.map(transformEntityResponse);
}

// Lấy thông tin tàu
export async function getShipInfo(): Promise<API.ShipDetail> {
  return await request<API.ShipDetail>(API_PATH_SHIP_INFO);
}

// Lấy cảnh báo
export async function queryAlarms(): Promise<API.AlarmResponse> {
  return await request<API.AlarmResponse>(API_GET_ALARMS);
}

// Lấy GPS
export async function getGPS() {
  return await request<API.GPSResonse>(API_GET_GPS);
}

// SOS
export async function getSos() { ... }
export async function deleteSos() { ... }
export async function sendSosMessage(message: string) { ... }

// Trackpoints (lịch sử di chuyển)
export async function queryShipTrackPoints() {
  return await request<API.ShipTrackPoint[]>(API_PATH_SHIP_TRACK_POINTS);
}

services/controller/TripController.ts

  • getTrip(): Lấy thông tin chuyến đi
  • updateTripStatus(): Cập nhật trạng thái chuyến đi
  • createHaul(): Tạo mẻ lưới mới
  • updateFishingLog(): Cập nhật nhật ký đánh cá

services/controller/MapController.ts

  • queryBanzones(): Lấy danh sách vùng cấm
  • getLayerInfo(): Lấy thông tin layer

services/controller/UserController.ts

  • Các API liên quan đến user

services/service/MapService.ts

export function getShipIcon(alarmLevel: number, isFishing: boolean) {
  // Trả về icon tàu tương ứng với:
  // - alarmLevel: 0-4 (mức độ cảnh báo)
  // - isFishing: đang đánh cá hay không
}

8. FOLDER SRC/PAGES/ - CÁC TRANG

pages/Auth/index.tsx - Trang đăng nhập

const LoginPage = () => {
  useEffect(() => {
    checkLogin(); // Kiểm tra đã login chưa
  }, []);

  const checkLogin = () => {
    const token = getToken();
    if (token && parseJwt(token).exp > now + 1 hour) {
      // Đã login → redirect home
      history.push(redirect || ROUTE_HOME);
    }
  };

  const handleLogin = async (values) => {
    const resp = await login(values);
    if (resp?.token) {
      setToken(resp.token);
      history.push(redirect || ROUTE_HOME);
    }
  };

  return (
    <LoginFormPage
      onFinish={handleLogin}
      backgroundVideoUrl="..."
    >
      <ProFormText name="username" />
      <ProFormText.Password name="password" />
    </LoginFormPage>
  );
}

Luồng:

1. Render form login
2. User nhập thông tin
3. Submit → gọi handleLogin()
4. Lưu token
5. Redirect về /map hoặc URL trước đó

pages/Home/index.tsx - Trang giám sát bản đồ

Cấu trúc component:

const HomePage = () => {
  const mapRef = useRef<HTMLDivElement>(null);
  const [isShipInfoOpen, setIsShipInfoOpen] = useState(false);
  const { gpsData, getGPSData } = useModel('getSos');

  const mapManagerRef = useRef(
    new MapManager(mapRef, {
      onFeatureClick, // Khi click vào tàu
      onFeaturesClick, // Khi click nhiều tàu
      onError,
    }),
  );

  useEffect(() => {
    getGPSData(); // Lấy GPS ban đầu

    // Polling mỗi 5s
    const interval = setInterval(() => {
      getGPSData();
      getEntitiesData(mapManager, gpsData);
      fetchAndAddTrackPoints(mapManager);
      fetchAndProcessEntities(mapManager);
    }, 5000);

    return () => clearInterval(interval);
  }, []);
};

Các hàm chính:

  1. addFeatureToMap() - Vẽ tàu lên bản đồ
const addFeatureToMap = (mapManager, gpsData, alarm) => {
  // Xóa tàu cũ
  mapManager.featureLayer.getSource()?.clear();

  // Thêm tàu mới với icon tương ứng
  mapManager.addPoint(
    [gpsData.lon, gpsData.lat],
    { bearing: gpsData.h },
    {
      icon: getShipIcon(alarm.level, gpsData.fishing),
      scale: 0.1,
      animate: isSos ? true : false, // SOS thì animate
    },
  );
};
  1. fetchAndProcessEntities() - Vẽ vùng cấm/vùng báo
const fetchAndProcessEntities = async () => {
  // Lấy entities (vùng cấm, vùng báo...)
  const entities = await queryEntities();

  // Với entity '50:2' (vùng cấm)
  const zones = JSON.parse(entity.valueString);
  zones.forEach((zone) => {
    // Vẽ polygon hoặc polyline lên bản đồ
    if (geom_type === 2) {
      mapManager.addLineString(...);
    } else if (geom_type === 1) {
      mapManager.addPolygon(...);
    }
  });
};
  1. fetchAndAddTrackPoints() - Vẽ lịch sử di chuyển
const fetchAndAddTrackPoints = async () => {
  // Lấy lịch sử di chuyển
  const trackpoints = await queryShipTrackPoints();

  // Vẽ đường đi
  mapManager.addLineString(
    trackpoints.map((p) => [p.lon, p.lat]),
    { id: MAP_TRACKPOINTS_ID },
    { strokeColor: 'blue', strokeWidth: 3 },
  );
};

Components con:

  • VietNamMap: Component bản đồ chính
  • GpsInfo: Hiển thị thông tin GPS
  • ShipInfo: Hiển thị thông tin tàu
  • SosButton: Nút gửi SOS
  • BaseMap: Base map component

Luồng hoạt động:

1. Mount component
2. Khởi tạo MapManager (OpenLayers)
3. Gọi getGPSData() lấy GPS hiện tại
4. Vẽ tàu lên bản đồ với vị trí GPS
5. Polling mỗi 5s:
   - Cập nhật GPS
   - Cập nhật cảnh báo (alarm)
   - Vẽ lại tàu với icon tương ứng
   - Vẽ trackpoints (lịch sử di chuyển)
   - Vẽ vùng cấm/vùng báo
6. FloatButton: Điều hướng sang Trip, hiển thị Ship Info
7. SosButton: Gửi SOS

pages/Trip/index.tsx - Trang quản lý chuyến đi

Cấu trúc component:

const DetailTrip = () => {
  const [alarmList, setAlarmList] = useState<API.Alarm[]>([]);
  const { data, getApi } = useModel('getTrip');

  const queryDataSource = async () => {
    const resp = await queryAlarms();
    if (resp.alarms.length > 0) {
      setAlarmList(resp.alarms);
    }
  };

  useEffect(() => {
    getApi(); // Lấy thông tin trip
    queryDataSource(); // Lấy alarms
  }, []);

  return (
    <PageContainer
      header={{
        title: data?.name || 'Chuyến đi',
        tags: <BadgeTripStatus status={data?.trip_status} />,
      }}
      extra={[
        <CreateNewHaulOrTrip
          onCallBack={(success) => {
            switch (success) {
              case STATUS.CREATE_FISHING_LOG_SUCCESS:
                message.success('Tạo mẻ lưới thành công');
                break;
              // ...
            }
          }}
        />,
      ]}
    >
      <ProCard split="vertical">
        {/* Bên trái: Danh sách cảnh báo */}
        <ProCard colSpan={5}>
          <AlarmTable alarmList={alarmList} />
        </ProCard>

        {/* Bên phải: Nội dung chính */}
        <ProCard colSpan={19}>
          <MainTripBody tripInfo={data} />
        </ProCard>
      </ProCard>

      <TripCancelOrFinishButton tripStatus={data?.trip_status} />
    </PageContainer>
  );
};

Components con:

  • AlarmTable: Bảng danh sách cảnh báo
  • BadgeTripStatus: Badge hiển thị trạng thái trip
  • CancelTrip: Modal hủy chuyến đi
  • CreateNewHaulOrTrip: Modal tạo mẻ lưới/chuyến đi mới
  • CreateOrUpdateFishingLog: Modal tạo/cập nhật nhật ký đánh cá
  • HaulFishList: Danh sách cá trong mẻ lưới
  • HaulTable: Bảng danh sách mẻ lưới
  • ListSkeleton: Loading skeleton
  • MainTripBody: Nội dung chính của trip
  • TripCancelOrFinishButton: Nút hủy/hoàn thành chuyến đi
  • TripCost: Chi phí chuyến đi
  • TripCrews: Thuyền viên
  • TripFishingGear: Ngư cụ

Luồng:

1. Load trip hiện tại
2. Load danh sách cảnh báo
3. Hiển thị:
   - Trạng thái trip (Badge)
   - Bảng cảnh báo (bên trái)
   - Thông tin trip (bên phải): Haul, Cost, Crews, FishingGear
4. Nút tạo Haul/Trip mới
5. Nút Cancel/Finish Trip

9. FOLDER SRC/COMPONENTS/ - COMPONENTS DÙNG CHUNG

components/403/403Page.tsx

  • Trang hiển thị khi không có quyền truy cập

components/404/404Page.tsx

  • Trang Not Found

components/500/500Page.tsx

  • Trang Server Error

components/Footer/Footer.tsx

  • Footer component dùng chung

components/Guide/

  • Component hướng dẫn sử dụng

10. FOLDER SRC/TYPES/ - TYPE DEFINITIONS

types/geojson.d.ts

  • Type definitions cho GeoJSON
  • Định nghĩa các type cho bản đồ

🔄 LUỒNG HOẠT ĐỘNG TỔNG THỂ

A. LUỒNG KHỞI ĐỘNG ỨNG DỤNG

1. User truy cập URL (vd: https://app.com/map)
2. UmiJS gọi getInitialState() trong app.tsx:
   ├─ Lấy token từ localStorage
   ├─ Parse JWT token
   ├─ Kiểm tra exp (expiry time)
   └─ Return { currentUser, exp } hoặc {}
3. Nếu không có token hoặc hết hạn:
   └─ Redirect sang /login
4. Nếu có token hợp lệ:
   ├─ Render Layout với avatar, menu
   └─ Render Route component (/map → HomePage)

B. LUỒNG ĐĂNG NHẬP

1. User truy cập /login
2. Render LoginPage
3. User nhập username/password
4. Submit → gọi login(body)
5. Backend trả về { token }
6. setToken(token) → lưu localStorage
7. history.push(ROUTE_HOME)
8. UmiJS gọi lại getInitialState()
9. Render HomePage

C. LUỒNG TRANG GIÁM SÁT (/map)

1. Render HomePage
2. Khởi tạo MapManager (OpenLayers map)
3. Gọi getGPSData() → lấy GPS hiện tại
4. Gọi getEntitiesData() → lấy alarms
5. Vẽ tàu lên bản đồ với icon tương ứng alarm level
6. Gọi fetchAndAddTrackPoints() → vẽ lịch sử di chuyển
7. Gọi fetchAndProcessEntities() → vẽ vùng cấm/vùng báo
8. Start polling mỗi 5s:
   ├─ Cập nhật GPS
   ├─ Cập nhật alarm
   ├─ Vẽ lại tàu
   ├─ Vẽ trackpoints
   └─ Vẽ vùng cấm
9. User có thể:
   ├─ Click FloatButton → xem GPS info
   ├─ Click vào tàu → xem ship info
   ├─ Click SOS button → gửi SOS
   └─ Click Trip button → sang trang /trip

D. LUỒNG TRANG CHUYẾN ĐI (/trip)

1. Render Trip page
2. Gọi getApi() → lấy trip hiện tại
3. Gọi queryAlarms() → lấy danh sách cảnh báo
4. Render:
   ├─ Header: tên trip, badge status
   ├─ Bên trái: AlarmTable
   └─ Bên phải: MainTripBody (Haul, Cost, Crews, FishingGear)
5. User có thể:
   ├─ Tạo Haul mới (CreateNewHaulOrTrip)
   ├─ Cập nhật Fishing Log
   ├─ Cancel Trip
   └─ Finish Trip

E. LUỒNG XỬ LÝ API REQUEST

1. Component gọi API function (vd: getGPS())
2. Request đi qua requestInterceptors:
   ├─ Lấy token từ localStorage
   └─ Gắn vào header: Authorization: {token}
3. Gửi request qua proxy:
   /api → http://192.168.30.102:81 (dev)
4. Nhận response
5. Nếu lỗi:
   ├─ errorHandler xử lý
   ├─ 401 → removeToken() → redirect login
   └─ Hiển thị message.error()
6. Nếu thành công:
   └─ Return data

F. LUỒNG XỬ LÝ SOS

1. User click SOS button
2. Hiển thị modal nhập message
3. User nhập message và confirm
4. Gọi sendSosMessage(message)
5. Backend xử lý SOS
6. Frontend polling sẽ detect SOS trong alarm
7. Icon tàu sẽ animate (nhấp nháy)
8. User có thể xóa SOS bằng deleteSos()

G. LUỒNG TẠO/CẬP NHẬT FISHING LOG

1. User click "Tạo mẻ lưới mới"
2. Hiển thị modal CreateOrUpdateFishingLog
3. User nhập thông tin:
   ├─ Thời gian thả/thu lưới
   ├─ Vị trí
   ├─ Loại cá
   └─ Số lượng
4. Submit → gọi API
5. Refresh data
6. Hiển thị message success/fail

🔑 CÁC ĐIỂM QUAN TRỌNG

1. Authentication Flow

  • JWT token lưu trong localStorage với key 'token'
  • Token được kiểm tra ở 3 nơi:
    • getInitialState(): Khi khởi động app
    • requestInterceptors: Mỗi request API
    • onPageChange: Mỗi lần chuyển trang
  • Token hết hạn (< 1 giờ) → auto logout
  • Token gắn vào header: Authorization: {token}

2. State Management

  • Sử dụng UmiJS Model (tương tự Redux nhưng đơn giản hơn)
  • Models chính:
    • getSos: Quản lý GPS data
    • getTrip: Quản lý Trip data
    • getAlarm: Quản lý Alarm data
    • global: State chung
  • Hook sử dụng: useModel('modelName')
  • Model tự động global, không cần Provider

3. Map Rendering (OpenLayers)

  • MapManager: Class quản lý bản đồ
  • Polling 5s để cập nhật real-time:
    • Vị trí tàu (GPS)
    • Cảnh báo (Alarms)
    • Trackpoints (lịch sử di chuyển)
    • Vùng cấm (Banzones)
  • Icon tàu thay đổi theo:
    • Alarm level (0-4): Màu icon khác nhau
    • Fishing status: Icon khác khi đang đánh cá
    • SOS status: Animate (nhấp nháy) khi SOS
  • Layers:
    • Feature layer: Tàu
    • Polyline layer: Đường đi, vùng cấm (line)
    • Polygon layer: Vùng cấm (polygon)

4. API Structure

  • Auth APIs:
    • POST /api/agent/login: Login
  • Device APIs:
    • GET /api/sgw/gps: Lấy GPS
    • GET /api/sgw/shipinfo: Thông tin tàu
    • GET /api/io/alarms: Danh sách cảnh báo
    • GET /api/io/entities: Danh sách entities
    • GET /api/sgw/trackpoints: Lịch sử di chuyển
    • GET/PUT/DELETE /api/sgw/sos: SOS
  • Trip APIs:
    • GET /api/sgw/trip: Thông tin trip
    • POST/PUT /api/sgw/tripState: Cập nhật trạng thái trip
    • POST/PUT /api/sgw/fishingLog: Fishing log
  • Map APIs:
    • GET /api/sgw/banzones: Vùng cấm
    • GET /api/sgw/geojsonlist: Danh sách layer
    • GET /api/sgw/geojson: Thông tin layer

5. Responsive Design

  • Sử dụng Ant Design Grid breakpoints:
    • xs: < 576px (mobile)
    • sm: ≥ 576px
    • md: ≥ 768px (tablet)
    • lg: ≥ 992px (desktop)
    • xl: ≥ 1200px
    • xxl: ≥ 1600px
  • ProCard split theo breakpoint:
    • Mobile: vertical split
    • Desktop: horizontal split

6. Error Handling

  • HTTP errors:
    • 401: Auto logout + redirect login
    • 403: Hiển thị 403 page
    • 404: Hiển thị 404 page
    • 500: Hiển thị 500 page
    • Other: Hiển thị message.error()
  • Request timeout: 20s
  • Polling error: Log ra console, tiếp tục polling

7. Data Flow

Component → useModel → API Controller → Request (with token)
→ Proxy → Backend → Response → Update Model → Re-render Component

8. Real-time Updates

  • Sử dụng polling (setInterval 5s) thay vì WebSocket
  • Các data được poll:
    • GPS location
    • Alarms
    • Trip status
    • Track points
    • Entities (banzones)

📦 DEPENDENCIES QUAN TRỌNG

Core Dependencies

  • @umijs/max (^4.5.0): Framework core của UmiJS

    • Tích hợp sẵn: routing, state management, request, layout
    • Plugin system mạnh mẽ
  • react (^18.x): UI library

  • typescript (^5.x): Type safety

UI Dependencies

  • antd (^5.4.0): UI component library

    • Components: Button, Form, Table, Modal, Message, etc.
  • @ant-design/pro-components (^2.4.4): Advanced components

    • ProTable: Advanced table
    • ProForm: Advanced form
    • ProCard: Advanced card
    • ProLayout: Layout system
    • PageContainer: Page wrapper
  • @ant-design/icons (^5.0.1): Icon library

    • Thousands of icons
    • Custom icon font support
  • tailwindcss (^3.x): Utility-first CSS

    • Rapid styling
    • Responsive design

Map Dependencies

  • ol (OpenLayers ^10.6.1): Map library
    • Vector layers
    • GeoJSON support
    • Interactive features
    • Animation support

Utility Dependencies

  • moment (^2.30.1): Date/time manipulation

    • Format dates
    • Calculate durations
    • Timezone support
  • classnames (^2.5.1): Conditional className

    • Dynamic className generation

Dev Dependencies

  • prettier (^2.x): Code formatter
  • prettier-plugin-organize-imports: Auto organize imports
  • prettier-plugin-packagejson: Format package.json
  • husky (^9.x): Git hooks
  • lint-staged (^13.x): Run linters on staged files
  • @types/react & @types/react-dom: TypeScript types

🚀 WORKFLOW DEVELOPMENT

Setup Project

# 1. Clone repository
git clone <repo-url>
cd FE-DEVICE-SGW

# 2. Install dependencies (sử dụng pnpm)
pnpm install

# 3. Setup environment
# Copy .env.example to .env.local (nếu có)

# 4. Run dev server
pnpm dev
# App chạy tại http://localhost:8000

Development Commands

# Chạy development server
pnpm dev

# Build production
pnpm build

# Format code
pnpm format

# Run specific command
pnpm start  # alias của pnpm dev

Git Workflow với Husky

# Pre-commit hook (tự động chạy)
- Lint staged files
- Format code với Prettier

# Commit
git add .
git commit -m "feat: add new feature"
# → Husky sẽ tự động format code trước khi commit

# Push
git push origin main

Environment Variables

# Set environment
REACT_APP_ENV=dev    # development
REACT_APP_ENV=test   # testing
REACT_APP_ENV=prod   # production

# Chạy với env cụ thể
REACT_APP_ENV=test pnpm dev

Proxy Configuration

File config/proxy.ts định nghĩa proxy cho mỗi môi trường:

  • dev: Point to local backend (192.168.30.102:81)
  • test: Point to test server
  • prod: Point to production server

Adding New Features

1. Thêm Route mới

File .umirc.ts:

routes: [
  // ... existing routes
  {
    name: 'New Page',
    path: '/new-page',
    component: './NewPage',
    icon: 'icon-name',
  },
],

2. Tạo Page mới

# Tạo folder và file
mkdir -p src/pages/NewPage
touch src/pages/NewPage/index.tsx
// src/pages/NewPage/index.tsx
const NewPage = () => {
  return <div>New Page Content</div>;
};

export default NewPage;

3. Thêm API mới

// src/constants/index.ts
export const API_NEW_ENDPOINT = '/api/new/endpoint';

// src/services/controller/NewController.ts
import { API_NEW_ENDPOINT } from '@/constants';
import { request } from '@umijs/max';

export async function getNewData() {
  return await request<API.NewDataType>(API_NEW_ENDPOINT);
}

4. Tạo Model mới (nếu cần global state)

// src/models/newModel.ts
import { useState, useCallback } from 'react';
import { getNewData } from '@/services/controller/NewController';

export default function useNewModel() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchData = useCallback(async () => {
    setLoading(true);
    try {
      const res = await getNewData();
      setData(res);
    } catch (error) {
      console.error(error);
    } finally {
      setLoading(false);
    }
  }, []);

  return { data, loading, fetchData };
}

5. Sử dụng Model trong Component

import { useModel } from '@umijs/max';

const MyComponent = () => {
  const { data, loading, fetchData } = useModel('newModel');

  useEffect(() => {
    fetchData();
  }, []);

  if (loading) return <Spin />;

  return <div>{data}</div>;
};

🐛 DEBUGGING & TROUBLESHOOTING

Common Issues

1. Token hết hạn

Triệu chứng: Auto logout, redirect về login
Nguyên nhân: Token exp < now + 1 hour
Giải pháp: Backend cần refresh token hoặc tăng exp time

2. CORS Error

Triệu chứng: Network error, blocked by CORS
Nguyên nhân: Backend chưa config CORS
Giải pháp: Sử dụng proxy trong config/proxy.ts

3. Map không hiển thị

Triệu chứng: Bản đồ trống, không có tiles
Nguyên nhân:
  - MapManager chưa init
  - GPS data null
  - OpenLayers version conflict
Giải pháp:
  - Check mapRef
  - Check gpsData
  - Check console errors

4. Polling không hoạt động

Triệu chứng: Data không cập nhật real-time
Nguyên nhân:
  - setInterval bị clear
  - API error không xử lý
Giải pháp:
  - Check cleanup trong useEffect
  - Check API response

5. Build Error

Triệu chứng: pnpm build failed
Nguyên nhân:
  - TypeScript errors
  - Missing dependencies
  - Import errors
Giải pháp:
  - Fix TypeScript errors
  - pnpm install
  - Check import paths

Debug Tips

1. Debug API Calls

// Trong Request.ts, thêm log
requestInterceptors: [
  (url: string, options: any) => {
    console.log('🚀 Request:', url, options);
    // ...
  },
],

// Check response
responseInterceptors: [
  (response) => {
    console.log('✅ Response:', response);
    return response;
  },
],

2. Debug State

// Trong component
const { data } = useModel('getTrip');

useEffect(() => {
  console.log('📊 Trip data:', data);
}, [data]);

3. Debug Map

// Trong HomePage
useEffect(() => {
  console.log('🗺️ MapManager:', mapManagerRef.current);
  console.log('📍 GPS Data:', gpsData);
}, [gpsData]);

4. React DevTools

  • Install React DevTools extension
  • Inspect component props/state
  • Profile performance

5. Network Tab

  • Check API calls
  • Check request headers (có Authorization không?)
  • Check response status

📊 PERFORMANCE OPTIMIZATION

1. Memo & Callback

// Sử dụng useMemo cho expensive calculations
const processedData = useMemo(() => {
  return heavyCalculation(data);
}, [data]);

// Sử dụng useCallback cho functions
const handleClick = useCallback(() => {
  doSomething();
}, []);

2. Lazy Loading

// Lazy load components
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

<Suspense fallback={<Spin />}>
  <HeavyComponent />
</Suspense>;

3. Debounce Polling

// Thay vì poll mỗi 5s, có thể tăng lên 10s hoặc 30s
const interval = setInterval(() => {
  fetchData();
}, 10000); // 10s

4. Optimize Map Rendering

// Clear old features trước khi add mới
mapManager.featureLayer.getSource()?.clear();

// Batch add features
const features = data.map((d) => createFeature(d));
mapManager.featureLayer.getSource()?.addFeatures(features);

5. Code Splitting

UmiJS tự động code splitting theo routes, nhưng có thể optimize thêm:

// Dynamic import
const module = await import('./module');

🔒 SECURITY BEST PRACTICES

1. Token Security

  • Lưu token trong localStorage (OK cho web app)
  • ⚠️ Không log token ra console
  • Check token expiry
  • Auto logout khi token hết hạn

2. API Security

  • Gắn Authorization header cho mọi request
  • Handle 401 error → logout
  • Validate response data
  • ⚠️ Không expose sensitive data trong logs

3. XSS Prevention

  • React tự động escape HTML
  • ⚠️ Không dùng dangerouslySetInnerHTML
  • Validate user input

4. CORS

  • Backend config CORS properly
  • Sử dụng proxy trong dev

📝 CODING CONVENTIONS

1. File Naming

  • Components: PascalCase (HomePage.tsx)
  • Utils: camelCase (formatUtils.ts)
  • Constants: camelCase (constants/index.ts)
  • Types: PascalCase (types/Api.d.ts)

2. Component Structure

// 1. Imports
import React from 'react';
import { useModel } from '@umijs/max';

// 2. Types
interface Props {
  // ...
}

// 3. Component
const MyComponent: React.FC<Props> = ({ prop1 }) => {
  // 4. Hooks
  const { data } = useModel('modelName');
  const [state, setState] = useState();

  // 5. Effects
  useEffect(() => {
    // ...
  }, []);

  // 6. Handlers
  const handleClick = () => {
    // ...
  };

  // 7. Render
  return <div>{/* ... */}</div>;
};

// 8. Export
export default MyComponent;

3. Import Order

// 1. React
import React, { useState, useEffect } from 'react';

// 2. External libraries
import { Button, Form } from 'antd';
import { useModel } from '@umijs/max';

// 3. Internal imports
import { getTrip } from '@/services/controller/TripController';
import { ROUTE_HOME } from '@/constants';
import MyComponent from '@/components/MyComponent';

// 4. Types
import type { API } from '@/services/typings';

// 5. Styles & Assets
import styles from './index.less';
import logo from './logo.png';

4. Naming Conventions

// Variables & functions: camelCase
const userData = {};
const fetchUserData = () => {};

// Components: PascalCase
const UserProfile = () => {};

// Constants: UPPER_SNAKE_CASE
const API_BASE_URL = 'https://api.example.com';

// Types & Interfaces: PascalCase
interface UserData {}
type UserRole = 'admin' | 'user';

// Private functions: _camelCase (optional)
const _helperFunction = () => {};

5. Comments

// ✅ Good: Giải thích WHY, not WHAT
// Polling mỗi 5s để cập nhật vị trí tàu real-time
const interval = setInterval(fetchGPS, 5000);

// ❌ Bad: Mô tả WHAT (code đã tự giải thích)
// Set interval 5000ms
const interval = setInterval(fetchGPS, 5000);

// ✅ TODO comments
// TODO: Implement WebSocket cho real-time updates
// FIXME: Memory leak khi unmount component

📈 SCALABILITY CONSIDERATIONS

1. Thêm nhiều tàu

Hiện tại chỉ theo dõi 1 tàu. Để mở rộng:

// Thay đổi từ single GPS sang array
const [shipsData, setShipsData] = useState<API.GPSResponse[]>([]);

// Polling cho tất cả tàu
shipsData.forEach(ship => {
  mapManager.addPoint([ship.lon, ship.lat], ...);
});

2. WebSocket thay Polling

// Thay thế setInterval bằng WebSocket
const ws = new WebSocket('wss://api.example.com/ws');

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updateMapWithNewData(data);
};

3. Caching

// Sử dụng React Query hoặc SWR cho caching
import { useQuery } from 'react-query';

const { data } = useQuery('trip', getTrip, {
  staleTime: 60000, // Cache 1 phút
  refetchInterval: 5000, // Refetch mỗi 5s
});

4. Pagination

// Cho AlarmTable, HaulTable
<ProTable
  request={async (params) => {
    const { current, pageSize } = params;
    const data = await getAlarms({ page: current, size: pageSize });
    return data;
  }}
  pagination={{
    defaultPageSize: 10,
  }}
/>

📚 USEFUL RESOURCES

Documentation

UmiJS Specific

Ant Design Pro


📝 TÓM TẮT CUỐI CÙNG

Điểm mạnh của dự án

Architecture rõ ràng: Separation of concerns tốt, dễ maintain UmiJS framework mạnh mẽ: Tích hợp sẵn nhiều tính năng Ant Design Pro: UI đẹp, responsive, nhiều components TypeScript: Type safety, giảm bugs Real-time updates: Polling 5s cho data mới Map integration: OpenLayers powerful Git hooks: Husky + lint-staged đảm bảo code quality

Điểm cần cải thiện

⚠️ WebSocket: Thay thế polling cho efficient hơn ⚠️ Caching: Implement caching cho API calls ⚠️ Error boundary: Thêm error boundaries cho components ⚠️ Unit tests: Viết tests cho critical functions ⚠️ Documentation: Thêm JSDoc cho functions/components ⚠️ Performance: Optimize map rendering với nhiều tàu

Tech Stack Summary

  • Framework: UmiJS (React-based)
  • Language: TypeScript
  • UI: Ant Design + Ant Design Pro Components
  • Styling: TailwindCSS + LESS
  • Map: OpenLayers
  • State: UmiJS Model
  • HTTP: UmiJS Request
  • Build: Webpack (via UmiJS)
  • Package Manager: pnpm

Main Features

  1. 🔐 Authentication: JWT-based login/logout
  2. 🗺️ Real-time Map: Hiển thị vị trí tàu real-time
  3. 🚨 Alarms & SOS: Hệ thống cảnh báo và SOS
  4. 🚢 Trip Management: Quản lý chuyến đi, mẻ lưới
  5. 📊 Dashboard: Thông tin GPS, tàu, thuyền viên
  6. 🚫 Banzones: Hiển thị vùng cấm trên bản đồ
  7. 📍 Track History: Lịch sử di chuyển của tàu

Dự án này là một hệ thống giám sát tàu cá chuyên nghiệp, được xây dựng với các công nghệ hiện đại và best practices. Code được tổ chức tốt, dễ maintain và mở rộng.

Chúc bạn học tốt và phát triển dự án thành công! 🚀