diff --git a/src/components/FlashMessage.tsx b/src/components/FlashMessage.tsx index 5d5f612..8a8187d 100644 --- a/src/components/FlashMessage.tsx +++ b/src/components/FlashMessage.tsx @@ -3,9 +3,10 @@ import Box from "@mui/material/Box"; import Alert from "@mui/material/Alert"; import { DeviceState } from "../types/device.types"; import { AuthState } from "../types/auth.types"; +import { FishboneState } from "../types/fishbone.types"; interface FlashMessageProps { - store: () => DeviceState | AuthState; + store: () => DeviceState | AuthState | FishboneState; } export default function FlashMessage(props: FlashMessageProps) { diff --git a/src/layouts/DashboardLayout.tsx b/src/layouts/DashboardLayout.tsx index e0c96ca..a8615d5 100644 --- a/src/layouts/DashboardLayout.tsx +++ b/src/layouts/DashboardLayout.tsx @@ -40,7 +40,9 @@ export default function DashboardLayout(props: Props) { [] = [ - { - header: "Device Code", - accessorKey: "device_code", - }, - { - header: "Device Type", - accessorKey: "device_type", - }, - { - header: "Address", - accessorKey: "address", - }, - { - header: "Total Port", - accessorKey: "port_amount", - }, - { - header: "Status", - accessorKey: "status", - cell: ({ row }) => ( - - {row.original.status} - - ), - }, - { - header: "", - accessorKey: "action", - enableSorting: false, - cell: ({ row }) => ( - <> - - - - - ), - }, - ]; + const columns: ColumnDef[] = useMemo( + () => [ + { + header: "Device Code", + accessorKey: "device_code", + }, + { + header: "Device Type", + accessorKey: "device_type", + }, + { + header: "Address", + accessorKey: "address", + }, + { + header: "Total Port", + accessorKey: "port_amount", + }, + { + header: "Total Used Port", + accessorKey: "total_used_port", + }, + { + header: "Status", + accessorKey: "status", + cell: ({ row }) => ( + + {row.original.status.toUpperCase()} + + ), + }, + { + header: "", + accessorKey: "action", + enableSorting: false, + cell: ({ row }) => ( + <> + + + + + ), + }, + ], + [] + ); useEffect(() => { if (cacheExpired()) { (async () => { - await getAll() - })() + await getAll(); + })(); } }, []); diff --git a/src/pages/Devices/Create.tsx b/src/pages/Devices/CreateOrEdit.tsx similarity index 98% rename from src/pages/Devices/Create.tsx rename to src/pages/Devices/CreateOrEdit.tsx index 3b814cd..70776f0 100644 --- a/src/pages/Devices/Create.tsx +++ b/src/pages/Devices/CreateOrEdit.tsx @@ -18,7 +18,7 @@ import { useDeviceStore } from "../../stores/deviceStore"; import { DeviceRequest } from "../../types/device.types"; import FlashMessage from "../../components/FlashMessage"; -export default function DeviceCreate() { +export default function DeviceCreateOrEdit() { useTitle("Devices"); const { pid } = useParams(); const { update, create, isLoading, find, clearDevice, device } = useDeviceStore(); diff --git a/src/pages/Devices/Form.tsx b/src/pages/Devices/Form.tsx index 764e5cf..4ba4a5f 100644 --- a/src/pages/Devices/Form.tsx +++ b/src/pages/Devices/Form.tsx @@ -1,28 +1,36 @@ -import Box from '@mui/material/Box'; -import Chip from '@mui/material/Chip'; +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 { useDeviceStore } from '../../stores/deviceStore'; +import { useEffect, useState } from "react"; +import { DeviceStatus } from "../../types/device.types"; +import { useDeviceStore } from "../../stores/deviceStore"; export default function Form() { - const { device, clearDevice } = useDeviceStore(); - const [select, setSelect] = useState('active'); + const { device, clearDevice, towerSelect, getTowerSelect } = useDeviceStore(); + const [select, setSelect] = useState("active"); + const [tower, setTower] = useState(""); const STATUS = ["active", "inactive", "maintenance"]; useEffect(() => { - setSelect(device?.status || 'active'); + (async () => { + await getTowerSelect(); + })(); + + if (device) { + setSelect(device.status); + setTower(device.tower_id as string); + } const clear = async () => { await clearDevice(); }; - + return () => { clear(); }; @@ -30,12 +38,17 @@ export default function Form() { return ( - {/* Device Code */} - - + + - {/* Total Port */} - + + - {/* Status */} - + + + Tower + + + + + Status setBackbone(e.target.value)} + > + {backboneSelect?.map((backbone, key) => ( + + {backbone.label.toUpperCase()} + + ))} + + + + + + Device Start + + + + + + Device End + + + + + + + + + + + + + + + ); +} diff --git a/src/repositories/deviceRepository.ts b/src/repositories/deviceRepository.ts index 6c52f0c..a8454fa 100644 --- a/src/repositories/deviceRepository.ts +++ b/src/repositories/deviceRepository.ts @@ -1,6 +1,7 @@ import { createAPI } from "../services/api"; -import { DeviceRepositoryProps, Device } from "../types/device.types"; +import { DeviceRepositoryProps, Device, IGetTowerSelect } from "../types/device.types"; import { ResponseApi } from "../types/response.types"; +import { SelectState } from "../types/store.types"; const api = createAPI(); @@ -19,9 +20,9 @@ export const deviceRepository: DeviceRepositoryProps = { latitude: "1", address: "Jl. Jend Sudirman No. XX", port_amount: 10, + total_used_port: 5, status: "active", created_at: "2024-01-01", - updated_at: "2024-01-01", } ]) }, @@ -39,6 +40,7 @@ export const deviceRepository: DeviceRepositoryProps = { address: "Jl. Jend Sudirman No. XX", port_amount: 10, status: "active", + tower_id: null, created_at: "2024-01-01", updated_at: "2024-01-01", }) @@ -53,4 +55,16 @@ export const deviceRepository: DeviceRepositoryProps = { const response = await api.put>(`/api/v1/devices/${id}`, data); return response.data; }, + + async getTowerSelect() { + // const response = await api.get>("/api/v1/devices/tower-select"); + + // return (response.data.data as IGetTowerSelect[])?.map((i: IGetTowerSelect) => ({label: i.tower_code, value: i.id})) as SelectState[]; + return Promise.resolve([ + { + label: "TWR-EX-001", + value: "1", + } + ]); + } }; diff --git a/src/repositories/fishboneRepository.ts b/src/repositories/fishboneRepository.ts new file mode 100644 index 0000000..2d607e0 --- /dev/null +++ b/src/repositories/fishboneRepository.ts @@ -0,0 +1,89 @@ +import { createAPI } from "../services/api"; +import { FishboneRepositoryProps, Fishbone, IGetBackboneSelect } from "../types/fishbone.types"; +import { ResponseApi } from "../types/response.types"; +import { SelectState } from "../types/store.types"; + +const api = createAPI(); + +export const fishboneRepository: FishboneRepositoryProps = { + async getAll() { + // const response = await api.get>("/api/v1/fishbones"); + + // return response.data.data as Fishbone[]; + + return Promise.resolve([ + { + id: "1", + backbone: "abced-efghi", + device_start: "DEV-S1", + device_end: "DEV-E1", + address: "Jl. Jend Sudirman No. XX", + core_amount: 10, + longitude: "1", + latitude: "1", + } + ]) + }, + + async getFishboneById(id: string) { + // const response = await api.get>(`/api/v1/fishbones/${id}`); + + // return response.data.data as Fishbone; + return Promise.resolve({ + id: "1", + // fishbone_code: "DV-EX-001", + backbone_id: "abced-efghi", + backbone_core: "16", + dev_start_id: "1", + dev_end_id: "2", + longitude: "1", + latitude: "1", + core_amount: 10, + address: "Jl. Jend Sudirman No. XX", + created_at: "2024-01-01", + }) + }, + + async createFishbone(data: Fishbone) { + const response = await api.post>("/api/v1/fishbones", data); + return response.data; + }, + + async updateFishbone(id: string, data: Fishbone) { + const response = await api.put>(`/api/v1/fishbones/${id}`, data); + return response.data; + }, + + async getBackboneSelect() { + // const response = await api.get>("/api/v1/fishbones/backbone"); + + // return (response.data.data as IGetBackboneSelect[])?.map((i: IGetBackboneSelect) => ({label: i.backbone_code, value: i.id})) as SelectState[]; + return Promise.resolve([ + { + label: "BB-001", + value: "abced-efghi", + }, + { + label: "BB-002", + value: "jklmn-opqrs", + } + ]); + }, + + async getDeviceSelect() { + // const response = await api.get>("/api/v1/fishbones/device"); + + // return (response.data.data as IGetBackboneSelect[])?.map((i: IGetBackboneSelect) => ({label: i.backbone_code, value: i.id})) as SelectState[]; + + return Promise.resolve([ + { + label: "DEV-S1", + value: "1", + }, + { + label: "DEV-S2", + value: "2", + } + ]); + } +}; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 0fcc180..1edbe3f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -9,7 +9,8 @@ import Devices from "../pages/Devices"; import Fishbone from "../pages/Fishbone"; import Towers from "../pages/Towers"; import RequiredAuth from "./RequiredAuth"; -import DeviceCreate from "../pages/Devices/Create"; +import DeviceCreateOrEdit from "../pages/Devices/CreateOrEdit"; +import FishboneCreateOrEdit from "../pages/Fishbone/CreateOrEdit"; export default function Routes() { return ( @@ -31,8 +32,11 @@ export default function Routes() { } /> {/* Create Page */} - } /> - } /> + } /> + } /> + + } /> + } /> diff --git a/src/stores/deviceStore.ts b/src/stores/deviceStore.ts index a188eca..d1dac75 100644 --- a/src/stores/deviceStore.ts +++ b/src/stores/deviceStore.ts @@ -15,6 +15,7 @@ export const useDeviceStore = create()( isLoading: false, error: null, lastUpdate: null, + towerSelect: [], getAll: async () => { set({ isLoading: true }); try { @@ -89,6 +90,23 @@ export const useDeviceStore = create()( set({ isLoading: false }); } }, + getTowerSelect: async () => { + set({ isLoading: true }); + try { + const towers = await deviceRepository.getTowerSelect(); + set({ towerSelect: 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 }), clearDevice: () => { set({ device: null }) diff --git a/src/stores/fishboneStore.ts b/src/stores/fishboneStore.ts new file mode 100644 index 0000000..d3f970a --- /dev/null +++ b/src/stores/fishboneStore.ts @@ -0,0 +1,146 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { fishboneRepository } from "../repositories/fishboneRepository"; +import { createJSONStorage } from "zustand/middleware"; +import { immer } from 'zustand/middleware/immer' +import { secureStorage } from "./storage"; +import { Fishbone, FishboneState } from "../types/fishbone.types"; +import dayjs from "dayjs"; + +export const useFishboneStore = create()( + persist( + immer((set, get) => ({ + data: [], + fishbone: null, + isLoading: false, + error: null, + lastUpdate: null, + backboneSelect: [], + deviceSelect: [], + getAll: async () => { + set({ isLoading: true }); + try { + const fishbones = await fishboneRepository.getAll(); + set({ data: fishbones, 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 fishbone = await fishboneRepository.getFishboneById(id); + + set({ fishbone: fishbone }); + } 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 ( + fishbone: Omit + ) => { + set({ isLoading: true }); + try { + await fishboneRepository.createFishbone(fishbone); + } 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, + fishbone: Omit + ) => { + set({ isLoading: true }); + try { + await fishboneRepository.updateFishbone(id, fishbone); + } 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 }); + } + }, + getBackboneSelect: async () => { + set({ isLoading: true }); + try { + const backboneSelect = await fishboneRepository.getBackboneSelect(); + set({ backboneSelect: backboneSelect }); + } 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 fishboneRepository.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 }), + clearFishbone: () => { + set({ fishbone: 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: "fishbones", storage: createJSONStorage(() => secureStorage) } + ) +); diff --git a/src/theme/components/index.ts b/src/theme/components/index.ts index 3c3cc7b..b276aad 100644 --- a/src/theme/components/index.ts +++ b/src/theme/components/index.ts @@ -96,7 +96,7 @@ export default function ComponentsOverrides(theme: Theme) { TableFooter(theme), TableHead(theme), TablePagination(), - TableRow(), + TableRow(theme), Tabs(), ToggleButton(theme), Tooltip(theme), diff --git a/src/types/device.types.ts b/src/types/device.types.ts index ccd33ca..56a514f 100644 --- a/src/types/device.types.ts +++ b/src/types/device.types.ts @@ -1,11 +1,12 @@ import { ResponseApi } from "./response.types"; -import { CacheState, ErrorState } from "./store.types"; +import { CacheState, ErrorState, SelectState } from "./store.types"; export interface DeviceRepositoryProps { - getAll: () => Promise; + getAll: () => Promise; getDeviceById: (id: string) => Promise; createDevice: (device: DeviceRequest) => Promise, "status">>; updateDevice: (id: string, device: DeviceRequest) => Promise, "status">>; + getTowerSelect: () => Promise; } export interface Device { @@ -16,6 +17,7 @@ export interface Device { latitude: string | number; port_amount: number; status: DeviceStatus; + tower_id: string | null; created_at: string; updated_at: string; } @@ -29,10 +31,12 @@ export interface DeviceRequest extends Omit Promise; getAll: () => Promise; find: (id: string) => Promise; create: (device: DeviceRequest) => Promise; @@ -40,7 +44,12 @@ export interface DeviceState extends ErrorState, CacheState { clearDevice: () => void; } -export interface DeviceErrorInputState { - username?: string | null; - password?: string | null; +export interface DeviceDataTable extends Pick { + address: string; + total_used_port: string | number; +} + +export interface IGetTowerSelect { + id: string; + tower_code: string; } \ No newline at end of file diff --git a/src/types/fishbone.types.ts b/src/types/fishbone.types.ts new file mode 100644 index 0000000..b330d98 --- /dev/null +++ b/src/types/fishbone.types.ts @@ -0,0 +1,62 @@ +import { ResponseApi } from "./response.types"; +import { CacheState, ErrorState, SelectState } from "./store.types"; + +export interface FishboneRepositoryProps { + getAll: () => Promise; + getFishboneById: (id: string) => Promise; + createFishbone: (fishbone: FishboneRequest) => Promise, "status">>; + updateFishbone: (id: string, fishbone: FishboneRequest) => Promise, "status">>; + getBackboneSelect: () => Promise; + getDeviceSelect: () => Promise; +} + +export interface Fishbone { + id: string; + backbone_id: string; + backbone_core: string; + dev_start_id: string; + dev_end_id: string; + longitude: string | number; + latitude: string | number; + core_amount: number; + created_at: string; +} + +export interface FishboneRequest extends Omit {} + +/** + * Fishbone State + * ============= + */ +export interface FishboneState extends ErrorState, CacheState { + data?: FishboneDataTable[]; + fishbone?: Fishbone | null; + isLoading: boolean; + lastUpdate: number | null; + backboneSelect: SelectState[] | null; + deviceSelect: SelectState[] | null; + getBackboneSelect: () => Promise; + getDeviceSelect: () => Promise; + getAll: () => Promise; + find: (id: string) => Promise; + create: (fishbone: FishboneRequest) => Promise; + update: (id: string, fishbone: FishboneRequest) => Promise; + clearFishbone: () => void; +} + +export interface FishboneDataTable extends Pick { + backbone: string; + address: string; + device_start: string; + device_end: string; +} + +export interface IGetBackboneSelect { + id: string; + backbone_code: string; +} + +export interface IGetDeviceSelect { + id: string; + device_code: string; +} \ No newline at end of file diff --git a/src/types/store.types.ts b/src/types/store.types.ts index 561ccfa..4cbad01 100644 --- a/src/types/store.types.ts +++ b/src/types/store.types.ts @@ -5,4 +5,9 @@ export interface ErrorState { export interface CacheState { cacheExpired: () => boolean; +} + +export interface SelectState { + value: string; + label: string; } \ No newline at end of file