feat: impl mock api for devices & fishbone

This commit is contained in:
HasanMu 2025-02-17 18:40:40 +07:00
parent 167ec6b943
commit f8aa6ad785
17 changed files with 790 additions and 130 deletions

View File

@ -3,9 +3,10 @@ import Box from "@mui/material/Box";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
import { DeviceState } from "../types/device.types"; import { DeviceState } from "../types/device.types";
import { AuthState } from "../types/auth.types"; import { AuthState } from "../types/auth.types";
import { FishboneState } from "../types/fishbone.types";
interface FlashMessageProps { interface FlashMessageProps {
store: () => DeviceState | AuthState; store: () => DeviceState | AuthState | FishboneState;
} }
export default function FlashMessage(props: FlashMessageProps) { export default function FlashMessage(props: FlashMessageProps) {

View File

@ -40,7 +40,9 @@ export default function DashboardLayout(props: Props) {
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<CssBaseline /> <CssBaseline />
<AppBar <AppBar
// color="default"
position="fixed" position="fixed"
enableColorOnDark
sx={{ sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` }, width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` }, ml: { sm: `${drawerWidth}px` },

View File

@ -15,8 +15,8 @@ import IconButton from "../components/IconButton";
import { Edit } from "iconsax-react"; import { Edit } from "iconsax-react";
import useTitle from "../hooks/useTitle"; import useTitle from "../hooks/useTitle";
import { useDeviceStore } from "../stores/deviceStore"; import { useDeviceStore } from "../stores/deviceStore";
import { Device } from "../types/device.types"; import { DeviceDataTable } from "../types/device.types";
import { useEffect } from "react"; import { useEffect, useMemo } from "react";
import FlashMessage from "../components/FlashMessage"; import FlashMessage from "../components/FlashMessage";
export default function Devices() { export default function Devices() {
@ -25,7 +25,8 @@ export default function Devices() {
const { data, cacheExpired, getAll } = useDeviceStore(); const { data, cacheExpired, getAll } = useDeviceStore();
// Define columns // Define columns
const columns: ColumnDef<Device>[] = [ const columns: ColumnDef<DeviceDataTable>[] = useMemo(
() => [
{ {
header: "Device Code", header: "Device Code",
accessorKey: "device_code", accessorKey: "device_code",
@ -42,6 +43,10 @@ export default function Devices() {
header: "Total Port", header: "Total Port",
accessorKey: "port_amount", accessorKey: "port_amount",
}, },
{
header: "Total Used Port",
accessorKey: "total_used_port",
},
{ {
header: "Status", header: "Status",
accessorKey: "status", accessorKey: "status",
@ -55,7 +60,7 @@ export default function Devices() {
: "warning.main" : "warning.main"
} }
> >
{row.original.status} {row.original.status.toUpperCase()}
</Typography> </Typography>
), ),
}, },
@ -65,19 +70,25 @@ export default function Devices() {
enableSorting: false, enableSorting: false,
cell: ({ row }) => ( cell: ({ row }) => (
<> <>
<IconButton variant="text" color="secondary" href={`/devices/${row.original.id}`}> <IconButton
variant="text"
color="secondary"
href={`/devices/${row.original.id}`}
>
<Edit variant="Bulk" color="currentColor" /> <Edit variant="Bulk" color="currentColor" />
</IconButton> </IconButton>
</> </>
), ),
}, },
]; ],
[]
);
useEffect(() => { useEffect(() => {
if (cacheExpired()) { if (cacheExpired()) {
(async () => { (async () => {
await getAll() await getAll();
})() })();
} }
}, []); }, []);

View File

@ -18,7 +18,7 @@ import { useDeviceStore } from "../../stores/deviceStore";
import { DeviceRequest } from "../../types/device.types"; import { DeviceRequest } from "../../types/device.types";
import FlashMessage from "../../components/FlashMessage"; import FlashMessage from "../../components/FlashMessage";
export default function DeviceCreate() { export default function DeviceCreateOrEdit() {
useTitle("Devices"); useTitle("Devices");
const { pid } = useParams(); const { pid } = useParams();
const { update, create, isLoading, find, clearDevice, device } = useDeviceStore(); const { update, create, isLoading, find, clearDevice, device } = useDeviceStore();

View File

@ -1,23 +1,31 @@
import Box from '@mui/material/Box'; import Box from "@mui/material/Box";
import Chip from '@mui/material/Chip'; import Chip from "@mui/material/Chip";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Grid from "@mui/material/Grid2"; import Grid from "@mui/material/Grid2";
import InputLabel from "@mui/material/InputLabel"; import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select"; import Select from "@mui/material/Select";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { DeviceStatus } from '../../types/device.types'; import { DeviceStatus } from "../../types/device.types";
import { useDeviceStore } from '../../stores/deviceStore'; import { useDeviceStore } from "../../stores/deviceStore";
export default function Form() { export default function Form() {
const { device, clearDevice } = useDeviceStore(); const { device, clearDevice, towerSelect, getTowerSelect } = useDeviceStore();
const [select, setSelect] = useState<DeviceStatus>('active'); const [select, setSelect] = useState<DeviceStatus>("active");
const [tower, setTower] = useState<string>("");
const STATUS = ["active", "inactive", "maintenance"]; const STATUS = ["active", "inactive", "maintenance"];
useEffect(() => { useEffect(() => {
setSelect(device?.status || 'active'); (async () => {
await getTowerSelect();
})();
if (device) {
setSelect(device.status);
setTower(device.tower_id as string);
}
const clear = async () => { const clear = async () => {
await clearDevice(); await clearDevice();
@ -30,12 +38,17 @@ export default function Form() {
return ( return (
<Grid container spacing={2}> <Grid container spacing={2}>
{/* Device Code */} <Grid size={{ md: 6, xs: 12 }}>
<Grid size={{ md: 4, xs: 12 }}> <TextField
<TextField fullWidth name='device_code' label="Device Code" variant="outlined" value={device?.device_code} /> fullWidth
name="device_code"
label="Device Code"
variant="outlined"
value={device?.device_code}
/>
</Grid> </Grid>
{/* Total Port */}
<Grid size={{ md: 4, xs: 12 }}> <Grid size={{ md: 6, xs: 12 }}>
<TextField <TextField
fullWidth fullWidth
name="port_count" name="port_count"
@ -46,8 +59,26 @@ export default function Form() {
/> />
</Grid> </Grid>
{/* Status */} <Grid size={{ md: 6, xs: 12 }}>
<Grid size={{ md: 4, xs: 12 }}> <FormControl fullWidth>
<InputLabel id="tower-label">Tower</InputLabel>
<Select
name="tower"
labelId="tower-label"
label="Tower"
value={tower}
onChange={(e) => setTower(e.target.value)}
>
{towerSelect?.map((tower, key) => (
<MenuItem key={key} value={tower.value}>
{tower.label.toUpperCase()}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ md: 6, xs: 12 }}>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel id="status-label">Status</InputLabel> <InputLabel id="status-label">Status</InputLabel>
<Select <Select

View File

@ -3,6 +3,7 @@ import Breadcrumbs from "@mui/material/Breadcrumbs";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent"; import CardContent from "@mui/material/CardContent";
import Divider from "@mui/material/Divider";
import Link from "@mui/material/Link"; import Link from "@mui/material/Link";
import Grid from "@mui/material/Grid2"; import Grid from "@mui/material/Grid2";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -13,24 +14,18 @@ import { ColumnDef } from "@tanstack/react-table";
import IconButton from "../components/IconButton"; import IconButton from "../components/IconButton";
import { Edit } from "iconsax-react"; import { Edit } from "iconsax-react";
import useTitle from "../hooks/useTitle"; import useTitle from "../hooks/useTitle";
import { useFishboneStore } from "../stores/fishboneStore";
interface UserData { import { FishboneDataTable } from "../types/fishbone.types";
backbone: string; import FlashMessage from "../components/FlashMessage";
device_start: string; import { useEffect, useMemo } from "react";
device_end: string;
total_core: string;
}
export default function Fishbone() { export default function Fishbone() {
useTitle("Fishbone"); useTitle("Fishbone");
const data: UserData[] = [ const { data, cacheExpired, getAll } = useFishboneStore();
{ backbone: 'BB-BDG-SMD', device_start: "DV-CODE-1", device_end: "DV-CODE-2", total_core: '24 Core', },
{ backbone: 'BB-BDG-SBG', device_start: "DV-CODE-3", device_end: "DV-CODE-4", total_core: '24 Core', },
];
// Define columns const columns: ColumnDef<FishboneDataTable>[] = useMemo(
const columns: ColumnDef<UserData>[] = [ () => [
{ {
header: "Backbone", header: "Backbone",
accessorKey: "backbone", accessorKey: "backbone",
@ -44,22 +39,40 @@ export default function Fishbone() {
accessorKey: "device_end", accessorKey: "device_end",
}, },
{ {
header: "Total Core", header: "Address",
accessorKey: "total_core", accessorKey: "address",
}, },
{ {
header: '', header: "Core used",
accessorKey: "core_amount",
},
{
header: "",
accessorKey: "action", accessorKey: "action",
enableSorting: false, enableSorting: false,
cell: () => ( cell: ({ row }) => (
<> <>
<IconButton variant="light" color="info" > <IconButton
variant="text"
color="secondary"
href={`/fishbone/${row.original.id}`}
>
<Edit variant="Bulk" color="currentColor" /> <Edit variant="Bulk" color="currentColor" />
</IconButton> </IconButton>
</> </>
) ),
},
],
[]
);
useEffect(() => {
if (cacheExpired()) {
(async () => {
await getAll();
})();
} }
]; }, []);
return ( return (
<Box component="main"> <Box component="main">
@ -68,7 +81,11 @@ export default function Fishbone() {
<Typography variant="h3">Fishbone</Typography> <Typography variant="h3">Fishbone</Typography>
</Grid> </Grid>
<Grid size="auto"> <Grid size="auto">
<Button variant="contained" startIcon={<AddIcon color="inherit" />}> <Button
href="/fishbone/create"
variant="contained"
startIcon={<AddIcon color="inherit" />}
>
Add New Add New
</Button> </Button>
</Grid> </Grid>
@ -82,12 +99,15 @@ export default function Fishbone() {
</Grid> </Grid>
</Grid> </Grid>
<Card sx={{ display: "flex" }}> <FlashMessage store={useFishboneStore} />
<CardContent sx={{ flex: "1 0 auto" }}>
<ReactTable <Card>
data={data} <Box sx={{ p: 2 }}>
columns={columns} <Typography variant="h5">List Devices</Typography>
/> </Box>
<Divider />
<CardContent sx={{ flex: "1 0 auto", pt: 2, px: 0 }}>
<ReactTable data={data || []} columns={columns} />
</CardContent> </CardContent>
</Card> </Card>
</Box> </Box>

View File

@ -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 { useFishboneStore } from "../../stores/fishboneStore";
import { FishboneRequest } from "../../types/fishbone.types";
export default function FishboneCreateOrEdit() {
useTitle("Fishbone");
const { pid } = useParams();
const { update, create, isLoading, find, clearFishbone } = useFishboneStore();
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 FishboneRequest;
if (IS_EDIT_PAGE) {
await update(pid!!, data);
} else {
await create(data);
}
}
useEffect(() => {
if (pid!!) {
(async () => {
await find(pid!!);
})()
} else {
(async () => {
await clearFishbone()
})()
}
}, [])
return (
<Box component="main">
<Grid container spacing={2} alignItems={"center"} mb={4}>
<Grid size="auto">
<Box display={"flex"} alignItems={"center"} gap={1}>
<IconButton variant="dashed" color="primary" href="/fishbone">
<Back variant="Outline" color="currentColor" />
</IconButton>
<Typography variant="h3">{IS_EDIT_PAGE ? `Edit Fishbone - ${pid}` : 'Create Fishbone'}</Typography>
</Box>
</Grid>
<Grid size={12}>
<Breadcrumbs aria-label="breadcrumb">
<Link underline="hover" color="inherit" href="/dashboard">
<Home />
</Link>
<Link underline="hover" color="inherit" href="/fishbone">
<Typography sx={{ color: "text.primary" }}>Fishbone</Typography>
</Link>
<Typography sx={{ color: "text.primary" }}>{IS_EDIT_PAGE ? pid : 'Create'}</Typography>
</Breadcrumbs>
</Grid>
</Grid>
<FlashMessage store={useFishboneStore} />
<Card component="form" onSubmit={onSubmit}>
<Box
sx={{ px: 3, py: 1 }}
display={"flex"}
justifyContent={"space-between"}
alignItems={"center"}
>
<Typography variant="h5">Fill the form</Typography>
<Button
disabled={isLoading}
type="submit"
variant="contained"
size="small"
startIcon={<Save color="inherit" />}
>
{IS_EDIT_PAGE ? "Save Changes" : "Create"}
</Button>
</Box>
<Divider />
<CardContent>
<Form />
</CardContent>
</Card>
</Box>
);
}

143
src/pages/Fishbone/Form.tsx Normal file
View File

@ -0,0 +1,143 @@
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 { useFishboneStore } from "../../stores/fishboneStore";
export default function Form() {
const {
fishbone,
clearFishbone,
backboneSelect,
deviceSelect,
getDeviceSelect,
getBackboneSelect,
} = useFishboneStore();
const [backbone, setBackbone] = useState<string>("");
const [deviceStart, setDeviceStart] = useState<string>("");
const [deviceEnd, setDeviceEnd] = useState<string>("");
useEffect(() => {
(async () => {
await getBackboneSelect();
await getDeviceSelect();
})();
if (fishbone) {
setBackbone(fishbone.backbone_id);
setDeviceStart(fishbone.dev_start_id);
setDeviceEnd(fishbone.dev_end_id);
}
const clear = async () => {
await clearFishbone();
};
return () => {
clear();
};
}, [fishbone]);
return (
<Grid container spacing={2}>
<Grid size={{ md: 4, xs: 12 }}>
<FormControl fullWidth>
<InputLabel id="backbone-label">Backbone</InputLabel>
<Select
name="backbone"
labelId="backbone-label"
label="Backbone"
value={backbone}
onChange={(e) => setBackbone(e.target.value)}
>
{backboneSelect?.map((backbone, key) => (
<MenuItem key={key} value={backbone.value}>
{backbone.label.toUpperCase()}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ md: 4, xs: 12 }}>
<FormControl fullWidth>
<InputLabel id="device-start-label">Device Start</InputLabel>
<Select
name="device-start"
labelId="device-start-label"
label="Device Start"
value={deviceStart}
onChange={(e) => setDeviceStart(e.target.value)}
>
{deviceSelect?.map((device, key) => (
<MenuItem key={key} value={device.value}>
{device.label.toUpperCase()}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ md: 4, xs: 12 }}>
<FormControl fullWidth>
<InputLabel id="device-end-label">Device End</InputLabel>
<Select
name="device-end"
labelId="device-end-label"
label="Device End"
value={deviceEnd}
onChange={(e) => setDeviceEnd(e.target.value)}
>
{deviceSelect
?.filter((b) => b.value != deviceStart)
.map((device, key) => (
<MenuItem key={key} value={device.value}>
{device.label.toUpperCase()}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ md: 12, xs: 12 }}>
<TextField
fullWidth
name="core_amount"
label="Core used"
variant="outlined"
type="number"
slotProps={{
htmlInput: Object.assign(
{ min: 1 },
fishbone?.backbone_core && { max: fishbone.backbone_core }
),
}}
value={fishbone?.core_amount}
/>
</Grid>
<Grid size={{ md: 6, xs: 12 }}>
<TextField
fullWidth
name="longitude"
label="Longitude"
variant="outlined"
type="number"
value={fishbone?.longitude}
/>
</Grid>
<Grid size={{ md: 6, xs: 12 }}>
<TextField
fullWidth
name="latitude"
label="Latitude"
variant="outlined"
type="number"
value={fishbone?.latitude}
/>
</Grid>
</Grid>
);
}

View File

@ -1,6 +1,7 @@
import { createAPI } from "../services/api"; 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 { ResponseApi } from "../types/response.types";
import { SelectState } from "../types/store.types";
const api = createAPI(); const api = createAPI();
@ -19,9 +20,9 @@ export const deviceRepository: DeviceRepositoryProps = {
latitude: "1", latitude: "1",
address: "Jl. Jend Sudirman No. XX", address: "Jl. Jend Sudirman No. XX",
port_amount: 10, port_amount: 10,
total_used_port: 5,
status: "active", status: "active",
created_at: "2024-01-01", created_at: "2024-01-01",
updated_at: "2024-01-01",
} }
]) ])
}, },
@ -39,6 +40,7 @@ export const deviceRepository: DeviceRepositoryProps = {
address: "Jl. Jend Sudirman No. XX", address: "Jl. Jend Sudirman No. XX",
port_amount: 10, port_amount: 10,
status: "active", status: "active",
tower_id: null,
created_at: "2024-01-01", created_at: "2024-01-01",
updated_at: "2024-01-01", updated_at: "2024-01-01",
}) })
@ -53,4 +55,16 @@ export const deviceRepository: DeviceRepositoryProps = {
const response = await api.put<ResponseApi<null>>(`/api/v1/devices/${id}`, data); const response = await api.put<ResponseApi<null>>(`/api/v1/devices/${id}`, data);
return response.data; return response.data;
}, },
async getTowerSelect() {
// const response = await api.get<ResponseApi<SelectState[]>>("/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",
}
]);
}
}; };

View File

@ -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<ResponseApi<Fishbone[]>>("/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<ResponseApi<Fishbone>>(`/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<ResponseApi<null>>("/api/v1/fishbones", data);
return response.data;
},
async updateFishbone(id: string, data: Fishbone) {
const response = await api.put<ResponseApi<null>>(`/api/v1/fishbones/${id}`, data);
return response.data;
},
async getBackboneSelect() {
// const response = await api.get<ResponseApi<SelectState[]>>("/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<ResponseApi<SelectState[]>>("/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",
}
]);
}
};

View File

@ -9,7 +9,8 @@ import Devices from "../pages/Devices";
import Fishbone from "../pages/Fishbone"; import Fishbone from "../pages/Fishbone";
import Towers from "../pages/Towers"; import Towers from "../pages/Towers";
import RequiredAuth from "./RequiredAuth"; 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() { export default function Routes() {
return ( return (
@ -31,8 +32,11 @@ export default function Routes() {
<Route path="/towers" element={<Towers />} /> <Route path="/towers" element={<Towers />} />
{/* Create Page */} {/* Create Page */}
<Route path="/devices/:pid" element={<DeviceCreate />} /> <Route path="/devices/:pid" element={<DeviceCreateOrEdit />} />
<Route path="/devices/create" element={<DeviceCreate />} /> <Route path="/devices/create" element={<DeviceCreateOrEdit />} />
<Route path="/fishbone/:pid" element={<FishboneCreateOrEdit />} />
<Route path="/fishbone/create" element={<FishboneCreateOrEdit />} />
</Route> </Route>
</Route> </Route>

View File

@ -15,6 +15,7 @@ export const useDeviceStore = create<DeviceState>()(
isLoading: false, isLoading: false,
error: null, error: null,
lastUpdate: null, lastUpdate: null,
towerSelect: [],
getAll: async () => { getAll: async () => {
set({ isLoading: true }); set({ isLoading: true });
try { try {
@ -89,6 +90,23 @@ export const useDeviceStore = create<DeviceState>()(
set({ isLoading: false }); 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 }), clearError: () => set({ error: null }),
clearDevice: () => { clearDevice: () => {
set({ device: null }) set({ device: null })

146
src/stores/fishboneStore.ts Normal file
View File

@ -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<FishboneState>()(
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<Fishbone, "id" & "created_at" & "updated_at">
) => {
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<Fishbone, "id" & "created_at" & "updated_at">
) => {
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) }
)
);

View File

@ -96,7 +96,7 @@ export default function ComponentsOverrides(theme: Theme) {
TableFooter(theme), TableFooter(theme),
TableHead(theme), TableHead(theme),
TablePagination(), TablePagination(),
TableRow(), TableRow(theme),
Tabs(), Tabs(),
ToggleButton(theme), ToggleButton(theme),
Tooltip(theme), Tooltip(theme),

View File

@ -1,11 +1,12 @@
import { ResponseApi } from "./response.types"; import { ResponseApi } from "./response.types";
import { CacheState, ErrorState } from "./store.types"; import { CacheState, ErrorState, SelectState } from "./store.types";
export interface DeviceRepositoryProps { export interface DeviceRepositoryProps {
getAll: () => Promise<Device[] | []>; getAll: () => Promise<DeviceDataTable[] | []>;
getDeviceById: (id: string) => Promise<Device>; getDeviceById: (id: string) => Promise<Device>;
createDevice: (device: DeviceRequest) => Promise<Pick<ResponseApi<any>, "status">>; createDevice: (device: DeviceRequest) => Promise<Pick<ResponseApi<any>, "status">>;
updateDevice: (id: string, device: DeviceRequest) => Promise<Pick<ResponseApi<any>, "status">>; updateDevice: (id: string, device: DeviceRequest) => Promise<Pick<ResponseApi<any>, "status">>;
getTowerSelect: () => Promise<SelectState[] | null>;
} }
export interface Device { export interface Device {
@ -16,6 +17,7 @@ export interface Device {
latitude: string | number; latitude: string | number;
port_amount: number; port_amount: number;
status: DeviceStatus; status: DeviceStatus;
tower_id: string | null;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
} }
@ -29,10 +31,12 @@ export interface DeviceRequest extends Omit<Device, "id" & "created_at" & "updat
* ============= * =============
*/ */
export interface DeviceState extends ErrorState, CacheState { export interface DeviceState extends ErrorState, CacheState {
data?: Device[]; data?: DeviceDataTable[];
device?: Device | null; device?: Device | null;
isLoading: boolean; isLoading: boolean;
lastUpdate: number | null; lastUpdate: number | null;
towerSelect: SelectState[] | null;
getTowerSelect: () => Promise<void>;
getAll: () => Promise<void>; getAll: () => Promise<void>;
find: (id: string) => Promise<void>; find: (id: string) => Promise<void>;
create: (device: DeviceRequest) => Promise<void>; create: (device: DeviceRequest) => Promise<void>;
@ -40,7 +44,12 @@ export interface DeviceState extends ErrorState, CacheState {
clearDevice: () => void; clearDevice: () => void;
} }
export interface DeviceErrorInputState { export interface DeviceDataTable extends Pick<Device, "id" | "device_code" | "device_type" | "port_amount" | "status"> {
username?: string | null; address: string;
password?: string | null; total_used_port: string | number;
}
export interface IGetTowerSelect {
id: string;
tower_code: string;
} }

View File

@ -0,0 +1,62 @@
import { ResponseApi } from "./response.types";
import { CacheState, ErrorState, SelectState } from "./store.types";
export interface FishboneRepositoryProps {
getAll: () => Promise<FishboneDataTable[] | []>;
getFishboneById: (id: string) => Promise<Fishbone>;
createFishbone: (fishbone: FishboneRequest) => Promise<Pick<ResponseApi<any>, "status">>;
updateFishbone: (id: string, fishbone: FishboneRequest) => Promise<Pick<ResponseApi<any>, "status">>;
getBackboneSelect: () => Promise<SelectState[] | null>;
getDeviceSelect: () => Promise<SelectState[] | null>;
}
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, "id" & "created_at" & "updated_at"> {}
/**
* 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<void>;
getDeviceSelect: () => Promise<void>;
getAll: () => Promise<void>;
find: (id: string) => Promise<void>;
create: (fishbone: FishboneRequest) => Promise<void>;
update: (id: string, fishbone: FishboneRequest) => Promise<void>;
clearFishbone: () => void;
}
export interface FishboneDataTable extends Pick<Fishbone, "id" | "core_amount" | "longitude" | "latitude"> {
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;
}

View File

@ -6,3 +6,8 @@ export interface ErrorState<T = undefined> {
export interface CacheState { export interface CacheState {
cacheExpired: () => boolean; cacheExpired: () => boolean;
} }
export interface SelectState {
value: string;
label: string;
}