diff --git a/src/assets/backbone.svg b/src/assets/backbone.svg new file mode 100644 index 0000000..44ed429 --- /dev/null +++ b/src/assets/backbone.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/assets/device.svg b/src/assets/device.svg new file mode 100644 index 0000000..b7e0a91 --- /dev/null +++ b/src/assets/device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/fishbone.svg b/src/assets/fishbone.svg new file mode 100644 index 0000000..cbffa8d --- /dev/null +++ b/src/assets/fishbone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/tower.svg b/src/assets/tower.svg new file mode 100644 index 0000000..8277339 --- /dev/null +++ b/src/assets/tower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/users.svg b/src/assets/users.svg new file mode 100644 index 0000000..2dfc863 --- /dev/null +++ b/src/assets/users.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/FlashMessage.tsx b/src/components/FlashMessage.tsx index 8a8187d..19b221c 100644 --- a/src/components/FlashMessage.tsx +++ b/src/components/FlashMessage.tsx @@ -4,9 +4,11 @@ import Alert from "@mui/material/Alert"; import { DeviceState } from "../types/device.types"; import { AuthState } from "../types/auth.types"; import { FishboneState } from "../types/fishbone.types"; +import { TowerState } from "../types/tower.types"; +import { BackboneState } from "../types/backbone.types"; interface FlashMessageProps { - store: () => DeviceState | AuthState | FishboneState; + store: () => DeviceState | AuthState | FishboneState | TowerState | BackboneState; } export default function FlashMessage(props: FlashMessageProps) { diff --git a/src/layouts/Dashboard/Sidebar.tsx b/src/layouts/Dashboard/Sidebar.tsx index ff327b6..7beede1 100644 --- a/src/layouts/Dashboard/Sidebar.tsx +++ b/src/layouts/Dashboard/Sidebar.tsx @@ -12,6 +12,11 @@ import { Link, Typography } from "@mui/material"; import { useLocation } from "react-router"; import { ReactSVG } from "react-svg"; import Speedometer from "../../assets/speedometer.svg"; +import Backbone from "../../assets/backbone.svg"; +import Fishbone from "../../assets/fishbone.svg"; +import Device from "../../assets/device.svg"; +import Tower from "../../assets/tower.svg"; +import Users from "../../assets/users.svg"; interface Props { /** @@ -37,22 +42,22 @@ const MENU = [ { title: "Backbone", link: "/backbone", - icon: Speedometer, + icon: Backbone, }, { title: "Fishbone", link: "/fishbone", - icon: Speedometer, + icon: Fishbone, }, { title: "Devices", link: "/devices", - icon: Speedometer, + icon: Device, }, { title: "Tower", link: "/towers", - icon: Speedometer, + icon: Tower, }, ], }, @@ -62,7 +67,7 @@ const MENU = [ { title: "User", link: "/user", - icon: Speedometer, + icon: Users, }, ], }, diff --git a/src/pages/Backbone.tsx b/src/pages/Backbone.tsx index c1ec796..67690f5 100644 --- a/src/pages/Backbone.tsx +++ b/src/pages/Backbone.tsx @@ -3,6 +3,7 @@ import Breadcrumbs from "@mui/material/Breadcrumbs"; import Button from "@mui/material/Button"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; +import Divider from "@mui/material/Divider"; import Link from "@mui/material/Link"; import Grid from "@mui/material/Grid2"; import Typography from "@mui/material/Typography"; @@ -13,6 +14,10 @@ import IconButton from "../components/IconButton"; import { Edit } from "iconsax-react"; import { ColumnDef } from "@tanstack/react-table"; import useTitle from "../hooks/useTitle"; +import FlashMessage from "../components/FlashMessage"; +import { useBackboneStore } from "../stores/backboneStore"; +import { BackboneDataTable } from "../types/backbone.types"; +import { useEffect, useMemo } from "react"; interface UserData { backbone: string; @@ -23,18 +28,15 @@ interface UserData { } export default function Backbone() { - useTitle("Backbone"); - - const data: UserData[] = [ - { backbone: "BB-BDG-SMD", device_start: "DV-CODE-1", device_end: "DV-CODE-2", total_core: "24 Core", total_fishbone: "3" }, - { backbone: "BB-BDG-SBG", device_start: "DV-CODE-3", device_end: "DV-CODE-4", total_core: "24 Core", total_fishbone: "5" }, - ]; + useTitle("Backbone"); + + const { data, cacheExpired, getAll } = useBackboneStore(); // Define columns - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = useMemo(() => [ { header: "Backbone", - accessorKey: "backbone", + accessorKey: "backbone_code", }, { header: "Device Start", @@ -46,7 +48,7 @@ export default function Backbone() { }, { header: "Total Core", - accessorKey: "total_core", + accessorKey: "core_amount", }, { header: "Total Fishbone", @@ -56,15 +58,23 @@ export default function Backbone() { header: "", accessorKey: "action", enableSorting: false, - cell: () => ( + cell: ({ row }) => ( <> - + ), }, - ]; + ], []); + + useEffect(() => { + if (cacheExpired()) { + (async () => { + await getAll(); + })(); + } + }, []); return ( @@ -73,7 +83,7 @@ export default function Backbone() { Backbone - @@ -87,12 +97,15 @@ export default function Backbone() { - - - + {/* */} + + + + List Backbone + + + + diff --git a/src/pages/Backbone/CreateOrEdit.tsx b/src/pages/Backbone/CreateOrEdit.tsx new file mode 100644 index 0000000..527ee8b --- /dev/null +++ b/src/pages/Backbone/CreateOrEdit.tsx @@ -0,0 +1,105 @@ +import Box from "@mui/material/Box"; +import Breadcrumbs from "@mui/material/Breadcrumbs"; +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import Divider from "@mui/material/Divider"; +import CardContent from "@mui/material/CardContent"; +import Grid from "@mui/material/Grid2"; +import Link from "@mui/material/Link"; +import Typography from "@mui/material/Typography"; +import { Home, Save } from "@mui/icons-material"; +import useTitle from "../../hooks/useTitle"; +import IconButton from "../../components/IconButton"; +import { Back } from "iconsax-react"; +import { useParams } from "react-router"; +import Form from "./Form"; +import { FormEvent, useEffect } from "react"; +import FlashMessage from "../../components/FlashMessage"; +import { useBackboneStore } from "../../stores/backboneStore"; +import { BackboneRequest } from "../../types/backbone.types"; + +export default function BackboneCreateOrEdit() { + useTitle("Backbone"); + const { pid } = useParams(); + const { update, create, isLoading, find, clearBackbone } = useBackboneStore(); + + const IS_EDIT_PAGE = pid!!; + + const onSubmit = async (e: FormEvent) => { + e.preventDefault(); + + const formData = new FormData(e.target as HTMLFormElement); + + const data = Object.fromEntries(formData.entries()) as unknown as BackboneRequest; + + if (IS_EDIT_PAGE) { + await update(pid!!, data); + } else { + await create(data); + } + } + + useEffect(() => { + if (pid!!) { + (async () => { + await find(pid!!); + })() + } else { + (async () => { + await clearBackbone() + })() + } + }, []) + + return ( + + + + + + + + {IS_EDIT_PAGE ? `Edit Backbone - ${pid}` : 'Create Backbone'} + + + + + + + + + Backbone + + {IS_EDIT_PAGE ? pid : 'Create'} + + + + + + + + + Fill the form + + + + +
+ + + + ); +} diff --git a/src/pages/Backbone/Form.tsx b/src/pages/Backbone/Form.tsx new file mode 100644 index 0000000..12c029e --- /dev/null +++ b/src/pages/Backbone/Form.tsx @@ -0,0 +1,121 @@ +import FormControl from "@mui/material/FormControl"; +import Grid from "@mui/material/Grid2"; +import InputLabel from "@mui/material/InputLabel"; +import MenuItem from "@mui/material/MenuItem"; +import Select from "@mui/material/Select"; +import TextField from "@mui/material/TextField"; +import { useEffect, useState } from "react"; +import { useBackboneStore } from "../../stores/backboneStore"; + +export default function Form() { + const { + backbone, + clearBackbone, + deviceSelect, + getDeviceSelect, + } = useBackboneStore(); + const [deviceStart, setDeviceStart] = useState(""); + const [deviceEnd, setDeviceEnd] = useState(""); + + useEffect(() => { + (async () => { + await getDeviceSelect(); + })(); + + if (backbone) { + setDeviceStart(backbone.dev_start_id as string); + setDeviceEnd(backbone.dev_end_id as string); + } + + const clear = async () => { + await clearBackbone(); + }; + + return () => { + clear(); + }; + }, [backbone]); + + return ( + + + + + + + + + + Device Start + + + + + + Device End + + + + + + + + + + + + ); +} diff --git a/src/pages/Towers.tsx b/src/pages/Towers.tsx index 78eb1ed..43c7826 100644 --- a/src/pages/Towers.tsx +++ b/src/pages/Towers.tsx @@ -3,6 +3,7 @@ import Breadcrumbs from "@mui/material/Breadcrumbs"; import Button from "@mui/material/Button"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; +import Divider from "@mui/material/Divider"; import Link from "@mui/material/Link"; import Grid from "@mui/material/Grid2"; import Typography from "@mui/material/Typography"; @@ -13,6 +14,10 @@ import { ColumnDef } from "@tanstack/react-table"; import IconButton from "../components/IconButton"; import { Edit } from "iconsax-react"; import useTitle from "../hooks/useTitle"; +import { useTowerStore } from "../stores/towerStore"; +import { useEffect, useMemo } from "react"; +import FlashMessage from "../components/FlashMessage"; +import { TowerDataTable } from "../types/tower.types"; interface UserData { tower_code: string; @@ -22,29 +27,18 @@ interface UserData { export default function Towers() { useTitle("Towers"); - - const data: UserData[] = [ - { tower_code: 'TW-CODE-1', device: "DV-CODE-1", address: "Jl. Contoh 1" }, - { tower_code: 'TW-CODE-2', device: "DV-CODE-2", address: "Jl. Contoh 2" }, - { tower_code: 'TW-CODE-3', device: "", address: "Jl. Contoh 3" }, - { tower_code: 'TW-CODE-4', device: "", address: "Jl. Contoh 4" }, - { tower_code: 'TW-CODE-5', device: "DV-CODE-5", address: "Jl. Contoh 5" }, - { tower_code: 'TW-CODE-6', device: "", address: "Jl. Contoh 6" }, - { tower_code: 'TW-CODE-7', device: "", address: "Jl. Contoh 7" }, - { tower_code: 'TW-CODE-8', device: "DV-CODE-8", address: "Jl. Contoh 8" }, - { tower_code: 'TW-CODE-9', device: "", address: "Jl. Contoh 9" }, - { tower_code: 'TW-CODE-10', device: "DV-CODE-10", address: "Jl. Contoh 10" }, - ]; + + const { data, cacheExpired, getAll } = useTowerStore(); // Define columns - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = useMemo(() => [ { header: "Tower Code", accessorKey: "tower_code", }, { header: "Device", - accessorKey: "device", + accessorKey: "device_code", }, { header: "Address", @@ -54,15 +48,23 @@ export default function Towers() { header: '', accessorKey: "action", enableSorting: false, - cell: () => ( + cell: ({ row }) => ( <> - + ) } - ]; + ], []); + + useEffect(() => { + if (cacheExpired()) { + (async () => { + await getAll(); + })(); + } + }, []); return ( @@ -71,7 +73,7 @@ export default function Towers() { Towers - @@ -85,12 +87,15 @@ export default function Towers() { - - - + + + + + List Towers + + + + diff --git a/src/pages/Towers/CreateOrEdit.tsx b/src/pages/Towers/CreateOrEdit.tsx new file mode 100644 index 0000000..81c56c8 --- /dev/null +++ b/src/pages/Towers/CreateOrEdit.tsx @@ -0,0 +1,105 @@ +import Box from "@mui/material/Box"; +import Breadcrumbs from "@mui/material/Breadcrumbs"; +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import Divider from "@mui/material/Divider"; +import CardContent from "@mui/material/CardContent"; +import Grid from "@mui/material/Grid2"; +import Link from "@mui/material/Link"; +import Typography from "@mui/material/Typography"; +import { Home, Save } from "@mui/icons-material"; +import useTitle from "../../hooks/useTitle"; +import IconButton from "../../components/IconButton"; +import { Back } from "iconsax-react"; +import { useParams } from "react-router"; +import Form from "./Form"; +import { FormEvent, useEffect } from "react"; +import { useTowerStore } from "../../stores/towerStore"; +import { TowerRequest } from "../../types/tower.types"; +import FlashMessage from "../../components/FlashMessage"; + +export default function TowerCreateOrEdit() { + useTitle("Towers"); + const { pid } = useParams(); + const { update, create, isLoading, find, clearTower, tower } = useTowerStore(); + + const IS_EDIT_PAGE = pid!!; + + const onSubmit = async (e: FormEvent) => { + e.preventDefault(); + + const formData = new FormData(e.target as HTMLFormElement); + + const data = Object.fromEntries(formData.entries()) as unknown as TowerRequest; + + if (IS_EDIT_PAGE) { + await update(pid!!, data); + } else { + await create(data); + } + } + + useEffect(() => { + if (pid!!) { + (async () => { + await find(pid!!); + })() + } else { + (async () => { + await clearTower() + })() + } + }, []) + + return ( + + + + + + + + {IS_EDIT_PAGE ? `Edit Tower - ${pid}` : 'Create Tower'} + + + + + + + + + Towers + + {IS_EDIT_PAGE ? pid : 'Create'} + + + + + + + + + Fill the form {tower?.tower_code} + + + + + + + + + ); +} diff --git a/src/pages/Towers/Form.tsx b/src/pages/Towers/Form.tsx new file mode 100644 index 0000000..d99087f --- /dev/null +++ b/src/pages/Towers/Form.tsx @@ -0,0 +1,89 @@ +import Box from "@mui/material/Box"; +import Chip from "@mui/material/Chip"; +import FormControl from "@mui/material/FormControl"; +import Grid from "@mui/material/Grid2"; +import InputLabel from "@mui/material/InputLabel"; +import MenuItem from "@mui/material/MenuItem"; +import Select from "@mui/material/Select"; +import TextField from "@mui/material/TextField"; +import { useEffect, useState } from "react"; +import { DeviceStatus } from "../../types/device.types"; +import { useTowerStore } from "../../stores/towerStore"; + +export default function Form() { + const { tower, clearTower, deviceSelect, getDeviceSelect } = useTowerStore(); + const [device, setDevice] = useState(""); + + useEffect(() => { + (async () => { + await getDeviceSelect(); + })(); + + if (tower) { + setDevice(tower.device_id as string); + } + + const clear = async () => { + await clearTower(); + }; + + return () => { + clear(); + }; + }, [tower]); + + return ( + + + + + + + + Device + + + + + + + + + + + + + ); +} diff --git a/src/repositories/backboneRepository.ts b/src/repositories/backboneRepository.ts new file mode 100644 index 0000000..6e96deb --- /dev/null +++ b/src/repositories/backboneRepository.ts @@ -0,0 +1,70 @@ +import { createAPI } from "../services/api"; +import { BackboneRepositoryProps, Backbone, IGetDeviceSelect } from "../types/backbone.types"; +import { ResponseApi } from "../types/response.types"; +import { SelectState } from "../types/store.types"; + +const api = createAPI(); + +export const backboneRepository: BackboneRepositoryProps = { + async getAll() { + // const response = await api.get>("/api/v1/backbones"); + + // return response.data.data as Backbone[]; + + return Promise.resolve([ + { + id: "1", + backbone_code: "BB-01", + device_start: "DEV-S1", + device_end: "DEV-E1", + address: "Jl. Jend Sudirman No. XX", + core_amount: 10, + longitude: "1", + latitude: "1", + total_fishbone: 2, + } + ]) + }, + async getBackboneById(id: string) { + // const response = await api.get>(`/api/v1/backbones/${id}`); + + // return response.data.data as Backbone; + return Promise.resolve({ + id: "1", + backbone_code: "BB-01", + dev_start_id: "1", + dev_end_id: "2", + core_amount: 10, + longitude: "1", + latitude: "1", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }) + }, + + async createBackbone(data: Backbone) { + const response = await api.post>("/api/v1/backbones", data); + return response.data; + }, + + async updateBackbone(id: string, data: Backbone) { + const response = await api.put>(`/api/v1/backbones/${id}`, data); + return response.data; + }, + + async getDeviceSelect() { + // const response = await api.get>("/api/v1/backbones/backbone"); + + // return (response.data.data as IGetBackboneSelect[])?.map((i: IGetBackboneSelect) => ({label: i.backbone_code, value: i.id})) as SelectState[]; + return Promise.resolve([ + { + label: "DV-EX-001", + value: "1", + }, + { + label: "DV-EX-002", + value: "2", + } + ]); + }, +}; diff --git a/src/repositories/deviceRepository.ts b/src/repositories/deviceRepository.ts index a8454fa..cf6ce76 100644 --- a/src/repositories/deviceRepository.ts +++ b/src/repositories/deviceRepository.ts @@ -23,6 +23,18 @@ export const deviceRepository: DeviceRepositoryProps = { total_used_port: 5, status: "active", created_at: "2024-01-01", + }, + { + id: "2", + device_code: "DV-EX-002", + device_type: "OTB", + longitude: "1", + latitude: "1", + address: "Jl. Jend Sudirman No. XX", + port_amount: 10, + total_used_port: 5, + status: "active", + created_at: "2024-01-01", } ]) }, diff --git a/src/repositories/towerRepository.ts b/src/repositories/towerRepository.ts new file mode 100644 index 0000000..8d93f9f --- /dev/null +++ b/src/repositories/towerRepository.ts @@ -0,0 +1,63 @@ +import { createAPI } from "../services/api"; +import { TowerRepositoryProps, Tower, IGetDeviceSelect } from "../types/tower.types"; +import { ResponseApi } from "../types/response.types"; +import { SelectState } from "../types/store.types"; + +const api = createAPI(); + +export const towerRepository: TowerRepositoryProps = { + async getAll() { + // const response = await api.get>("/api/v1/towers"); + + // return response.data.data as Tower[]; + + return Promise.resolve( [ + { + id: "1", + tower_code: "TW-001", + longitude: "1", + latitude: "1", + device_code: "DV-EX-001", + address: "Jl. Jend Sudirman No. XX", + created_at: "2024-01-01", + } + ]) + }, + + async getTowerById(id: string) { + // const response = await api.get>(`/api/v1/towers/${id}`); + + // return response.data.data as Tower; + return Promise.resolve({ + id: "1", + tower_code: "TW-001", + longitude: "1", + latitude: "1", + device_id: "1", + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }) + }, + + async createTower(data: Tower) { + const response = await api.post>("/api/v1/towers", data); + return response.data; + }, + + async updateTower(id: string, data: Tower) { + const response = await api.put>(`/api/v1/towers/${id}`, data); + return response.data; + }, + + async getDeviceSelect() { + // const response = await api.get>("/api/v1/towers/tower-select"); + + // return (response.data.data as IGetDeviceSelect[])?.map((i: IGetDeviceSelect) => ({label: i.tower_code, value: i.id})) as SelectState[]; + return Promise.resolve([ + { + label: "DV-EX-001", + value: "1", + } + ]); + } +}; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 1edbe3f..944edbd 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,5 +1,4 @@ -import { Routes as RRRoutes, Route } from "react-router"; -import App from "../App"; +import { Navigate, Routes as RRRoutes, Route } from "react-router"; import AuthLayout from "../layouts/AuthLayout"; import Login from "../pages/auth/Login"; import Dashboard from "../pages/Dashboard"; @@ -11,6 +10,8 @@ import Towers from "../pages/Towers"; import RequiredAuth from "./RequiredAuth"; import DeviceCreateOrEdit from "../pages/Devices/CreateOrEdit"; import FishboneCreateOrEdit from "../pages/Fishbone/CreateOrEdit"; +import TowerCreateOrEdit from "../pages/Towers/CreateOrEdit"; +import BackboneCreateOrEdit from "../pages/Backbone/CreateOrEdit"; export default function Routes() { return ( @@ -22,7 +23,7 @@ export default function Routes() { }> - } /> + } /> }> } /> @@ -37,6 +38,12 @@ export default function Routes() { } /> } /> + + } /> + } /> + + } /> + } /> diff --git a/src/stores/backboneStore.ts b/src/stores/backboneStore.ts new file mode 100644 index 0000000..55a3631 --- /dev/null +++ b/src/stores/backboneStore.ts @@ -0,0 +1,129 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { backboneRepository } from "../repositories/backboneRepository"; +import { createJSONStorage } from "zustand/middleware"; +import { immer } from 'zustand/middleware/immer' +import { secureStorage } from "./storage"; +import { Backbone, BackboneState } from "../types/backbone.types"; +import dayjs from "dayjs"; + +export const useBackboneStore = create()( + persist( + immer((set, get) => ({ + data: [], + backbone: null, + isLoading: false, + error: null, + lastUpdate: null, + backboneSelect: [], + deviceSelect: [], + getAll: async () => { + set({ isLoading: true }); + try { + const backbones = await backboneRepository.getAll(); + set({ data: backbones, lastUpdate: dayjs().valueOf() }); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response?.status?.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false, lastUpdate: dayjs().valueOf() }); + } + }, + find: async (id: string) => { + set({ isLoading: true }); + try { + const backbone = await backboneRepository.getBackboneById(id); + + set({ backbone: backbone }); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + create: async ( + backbone: Omit + ) => { + set({ isLoading: true }); + try { + await backboneRepository.createBackbone(backbone); + } catch (error: any) { + console.log(error); + + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + update: async ( + id: string, + backbone: Omit + ) => { + set({ isLoading: true }); + try { + await backboneRepository.updateBackbone(id, backbone); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + getDeviceSelect: async () => { + set({ isLoading: true }); + try { + const deviceSelect = await backboneRepository.getDeviceSelect(); + set({ deviceSelect: deviceSelect }); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + clearError: () => set({ error: null }), + clearBackbone: () => { + set({ backbone: null }) + }, + cacheExpired: () => { + return ( + get().lastUpdate === null || + dayjs(get().lastUpdate).isBefore( + dayjs().subtract( + import.meta.env.VITE_APP_CACHE_TIME_VALUE, + import.meta.env.VITE_APP_CACHE_TIME_UNIT + ) + ) + ); + }, + })), + { name: "backbones", storage: createJSONStorage(() => secureStorage) } + ) +); diff --git a/src/stores/towerStore.ts b/src/stores/towerStore.ts new file mode 100644 index 0000000..84afedf --- /dev/null +++ b/src/stores/towerStore.ts @@ -0,0 +1,128 @@ +import { create } from "zustand"; +import { towerRepository } from "../repositories/towerRepository"; +import { persist } from "zustand/middleware"; +import { createJSONStorage } from "zustand/middleware"; +import { immer } from "zustand/middleware/immer"; +import { secureStorage } from "./storage"; +import { Tower, TowerState } from "../types/tower.types"; +import dayjs from "dayjs"; + +export const useTowerStore = create()( + persist( + immer((set, get) => ({ + data: [], + tower: null, + isLoading: false, + error: null, + lastUpdate: null, + deviceSelect: [], + getAll: async () => { + set({ isLoading: true }); + try { + const towers = await towerRepository.getAll(); + set({ data: towers, lastUpdate: dayjs().valueOf() }); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response?.status?.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false, lastUpdate: dayjs().valueOf() }); + } + }, + find: async (id: string) => { + set({ isLoading: true }); + try { + const tower = await towerRepository.getTowerById(id); + + set({ tower: tower }); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + create: async ( + tower: Omit + ) => { + set({ isLoading: true }); + try { + await towerRepository.createTower(tower); + } catch (error: any) { + console.log(error); + + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + update: async ( + id: string, + tower: Omit + ) => { + set({ isLoading: true }); + try { + await towerRepository.updateTower(id, tower); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response.status.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + getDeviceSelect: async () => { + set({ isLoading: true }); + try { + const towers = await towerRepository.getDeviceSelect(); + set({ deviceSelect: towers }); + } catch (error: any) { + if (error.status === 401) { + set({ error: error.response?.status?.description }); + } else { + set({ + error: error.response?.status?.description || error.message, + }); + } + } finally { + set({ isLoading: false }); + } + }, + clearError: () => set({ error: null }), + clearTower: () => { + set({ tower: null }); + }, + cacheExpired: () => { + return ( + get().lastUpdate === null || + dayjs(get().lastUpdate).isBefore( + dayjs().subtract( + import.meta.env.VITE_APP_CACHE_TIME_VALUE, + import.meta.env.VITE_APP_CACHE_TIME_UNIT + ) + ) + ); + }, + })), + { name: "towers", storage: createJSONStorage(() => secureStorage) } + ) +); diff --git a/src/theme/index.tsx b/src/theme/index.tsx index 394f373..d4701df 100644 --- a/src/theme/index.tsx +++ b/src/theme/index.tsx @@ -46,7 +46,7 @@ export default function ThemeCustomization({ } } - const theme = Palette(ThemeMode.LIGHT); + const theme = Palette(themeMode); const themeTypography: TypographyVariantsOptions = useMemo( diff --git a/src/types/backbone.types.ts b/src/types/backbone.types.ts new file mode 100644 index 0000000..efcdc3c --- /dev/null +++ b/src/types/backbone.types.ts @@ -0,0 +1,53 @@ +import { ResponseApi } from "./response.types"; +import { CacheState, ErrorState, SelectState } from "./store.types"; + +export interface BackboneRepositoryProps { + getAll: () => Promise; + getBackboneById: (id: string) => Promise; + createBackbone: (backbone: BackboneRequest) => Promise, "status">>; + updateBackbone: (id: string, backbone: BackboneRequest) => Promise, "status">>; + getDeviceSelect: () => Promise; +} + +export interface Backbone { + id: string; + backbone_code: string; + longitude: string | number; + latitude: string | number; + dev_start_id?: string | number | null; + dev_end_id?: string | number | null; + core_amount: string | number; + created_at: string; + updated_at: string; +} + +export interface BackboneRequest extends Omit {} + +/** + * Backbone State + * ============= + */ +export interface BackboneState extends ErrorState, CacheState { + data?: BackboneDataTable[]; + backbone?: Backbone | null; + isLoading: boolean; + lastUpdate: number | null; + deviceSelect: SelectState[] | null; + getDeviceSelect: () => Promise; + getAll: () => Promise; + find: (id: string) => Promise; + create: (backbone: BackboneRequest) => Promise; + update: (id: string, backbone: BackboneRequest) => Promise; + clearBackbone: () => void; +} + +export interface BackboneDataTable extends Pick { + device_start: string; + device_end: string; + total_fishbone: string | number; +} + +export interface IGetDeviceSelect { + id: string; + device_code: string; +} \ No newline at end of file diff --git a/src/types/tower.types.ts b/src/types/tower.types.ts new file mode 100644 index 0000000..b7108c8 --- /dev/null +++ b/src/types/tower.types.ts @@ -0,0 +1,50 @@ +import { ResponseApi } from "./response.types"; +import { CacheState, ErrorState, SelectState } from "./store.types"; + +export interface TowerRepositoryProps { + getAll: () => Promise; + getTowerById: (id: string) => Promise; + createTower: (tower: TowerRequest) => Promise, "status">>; + updateTower: (id: string, tower: TowerRequest) => Promise, "status">>; + getDeviceSelect: () => Promise; +} + +export interface Tower { + id: string; + tower_code: string; + longitude: string | number; + latitude: string | number; + device_id?: string | number | null + created_at: string; + updated_at: string; +} + +export interface TowerRequest extends Omit {} + +/** + * Tower State + * ============= + */ +export interface TowerState extends ErrorState, CacheState { + data?: TowerDataTable[]; + tower?: Tower | null; + isLoading: boolean; + lastUpdate: number | null; + deviceSelect: SelectState[] | null; + getDeviceSelect: () => Promise; + getAll: () => Promise; + find: (id: string) => Promise; + create: (tower: TowerRequest) => Promise; + update: (id: string, tower: TowerRequest) => Promise; + clearTower: () => void; +} + +export interface TowerDataTable extends Pick { + address: string; + device_code: string; +} + +export interface IGetDeviceSelect { + id: string; + device_code: string; +} \ No newline at end of file