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 { 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) {

View File

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

View File

@ -15,8 +15,8 @@ import IconButton from "../components/IconButton";
import { Edit } from "iconsax-react";
import useTitle from "../hooks/useTitle";
import { useDeviceStore } from "../stores/deviceStore";
import { Device } from "../types/device.types";
import { useEffect } from "react";
import { DeviceDataTable } from "../types/device.types";
import { useEffect, useMemo } from "react";
import FlashMessage from "../components/FlashMessage";
export default function Devices() {
@ -25,59 +25,70 @@ export default function Devices() {
const { data, cacheExpired, getAll } = useDeviceStore();
// Define columns
const columns: ColumnDef<Device>[] = [
{
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 }) => (
<Typography
color={
row.original.status.toLowerCase() === "active"
? "success.main"
: row.original.status.toLowerCase() === "inactive"
? "error.main"
: "warning.main"
}
>
{row.original.status}
</Typography>
),
},
{
header: "",
accessorKey: "action",
enableSorting: false,
cell: ({ row }) => (
<>
<IconButton variant="text" color="secondary" href={`/devices/${row.original.id}`}>
<Edit variant="Bulk" color="currentColor" />
</IconButton>
</>
),
},
];
const columns: ColumnDef<DeviceDataTable>[] = 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 }) => (
<Typography
color={
row.original.status.toLowerCase() === "active"
? "success.main"
: row.original.status.toLowerCase() === "inactive"
? "error.main"
: "warning.main"
}
>
{row.original.status.toUpperCase()}
</Typography>
),
},
{
header: "",
accessorKey: "action",
enableSorting: false,
cell: ({ row }) => (
<>
<IconButton
variant="text"
color="secondary"
href={`/devices/${row.original.id}`}
>
<Edit variant="Bulk" color="currentColor" />
</IconButton>
</>
),
},
],
[]
);
useEffect(() => {
if (cacheExpired()) {
(async () => {
await getAll()
})()
await getAll();
})();
}
}, []);

View File

@ -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();

View File

@ -1,23 +1,31 @@
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<DeviceStatus>('active');
const { device, clearDevice, towerSelect, getTowerSelect } = useDeviceStore();
const [select, setSelect] = useState<DeviceStatus>("active");
const [tower, setTower] = useState<string>("");
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();
@ -30,12 +38,17 @@ export default function Form() {
return (
<Grid container spacing={2}>
{/* Device Code */}
<Grid size={{ md: 4, xs: 12 }}>
<TextField fullWidth name='device_code' label="Device Code" variant="outlined" value={device?.device_code} />
<Grid size={{ md: 6, xs: 12 }}>
<TextField
fullWidth
name="device_code"
label="Device Code"
variant="outlined"
value={device?.device_code}
/>
</Grid>
{/* Total Port */}
<Grid size={{ md: 4, xs: 12 }}>
<Grid size={{ md: 6, xs: 12 }}>
<TextField
fullWidth
name="port_count"
@ -46,8 +59,26 @@ export default function Form() {
/>
</Grid>
{/* Status */}
<Grid size={{ md: 4, xs: 12 }}>
<Grid size={{ md: 6, 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>
<InputLabel id="status-label">Status</InputLabel>
<Select

View File

@ -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,53 +14,65 @@ import { ColumnDef } from "@tanstack/react-table";
import IconButton from "../components/IconButton";
import { Edit } from "iconsax-react";
import useTitle from "../hooks/useTitle";
interface UserData {
backbone: string;
device_start: string;
device_end: string;
total_core: string;
}
import { useFishboneStore } from "../stores/fishboneStore";
import { FishboneDataTable } from "../types/fishbone.types";
import FlashMessage from "../components/FlashMessage";
import { useEffect, useMemo } from "react";
export default function Fishbone() {
useTitle("Fishbone");
const data: UserData[] = [
{ 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', },
];
const { data, cacheExpired, getAll } = useFishboneStore();
// Define columns
const columns: ColumnDef<UserData>[] = [
{
header: "Backbone",
accessorKey: "backbone",
},
{
header: "Device Start",
accessorKey: "device_start",
},
{
header: "Device End",
accessorKey: "device_end",
},
{
header: "Total Core",
accessorKey: "total_core",
},
{
header: '',
accessorKey: "action",
enableSorting: false,
cell: () => (
<>
<IconButton variant="light" color="info" >
<Edit variant="Bulk" color="currentColor" />
</IconButton>
</>
)
const columns: ColumnDef<FishboneDataTable>[] = useMemo(
() => [
{
header: "Backbone",
accessorKey: "backbone",
},
{
header: "Device Start",
accessorKey: "device_start",
},
{
header: "Device End",
accessorKey: "device_end",
},
{
header: "Address",
accessorKey: "address",
},
{
header: "Core used",
accessorKey: "core_amount",
},
{
header: "",
accessorKey: "action",
enableSorting: false,
cell: ({ row }) => (
<>
<IconButton
variant="text"
color="secondary"
href={`/fishbone/${row.original.id}`}
>
<Edit variant="Bulk" color="currentColor" />
</IconButton>
</>
),
},
],
[]
);
useEffect(() => {
if (cacheExpired()) {
(async () => {
await getAll();
})();
}
];
}, []);
return (
<Box component="main">
@ -68,7 +81,11 @@ export default function Fishbone() {
<Typography variant="h3">Fishbone</Typography>
</Grid>
<Grid size="auto">
<Button variant="contained" startIcon={<AddIcon color="inherit" />}>
<Button
href="/fishbone/create"
variant="contained"
startIcon={<AddIcon color="inherit" />}
>
Add New
</Button>
</Grid>
@ -82,12 +99,15 @@ export default function Fishbone() {
</Grid>
</Grid>
<Card sx={{ display: "flex" }}>
<CardContent sx={{ flex: "1 0 auto" }}>
<ReactTable
data={data}
columns={columns}
/>
<FlashMessage store={useFishboneStore} />
<Card>
<Box sx={{ p: 2 }}>
<Typography variant="h5">List Devices</Typography>
</Box>
<Divider />
<CardContent sx={{ flex: "1 0 auto", pt: 2, px: 0 }}>
<ReactTable data={data || []} columns={columns} />
</CardContent>
</Card>
</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 { 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<ResponseApi<null>>(`/api/v1/devices/${id}`, 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 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() {
<Route path="/towers" element={<Towers />} />
{/* Create Page */}
<Route path="/devices/:pid" element={<DeviceCreate />} />
<Route path="/devices/create" element={<DeviceCreate />} />
<Route path="/devices/:pid" element={<DeviceCreateOrEdit />} />
<Route path="/devices/create" element={<DeviceCreateOrEdit />} />
<Route path="/fishbone/:pid" element={<FishboneCreateOrEdit />} />
<Route path="/fishbone/create" element={<FishboneCreateOrEdit />} />
</Route>
</Route>

View File

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

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),
TableHead(theme),
TablePagination(),
TableRow(),
TableRow(theme),
Tabs(),
ToggleButton(theme),
Tooltip(theme),

View File

@ -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<Device[] | []>;
getAll: () => Promise<DeviceDataTable[] | []>;
getDeviceById: (id: string) => Promise<Device>;
createDevice: (device: DeviceRequest) => Promise<Pick<ResponseApi<any>, "status">>;
updateDevice: (id: string, device: DeviceRequest) => Promise<Pick<ResponseApi<any>, "status">>;
getTowerSelect: () => Promise<SelectState[] | null>;
}
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<Device, "id" & "created_at" & "updat
* =============
*/
export interface DeviceState extends ErrorState, CacheState {
data?: Device[];
data?: DeviceDataTable[];
device?: Device | null;
isLoading: boolean;
lastUpdate: number | null;
towerSelect: SelectState[] | null;
getTowerSelect: () => Promise<void>;
getAll: () => Promise<void>;
find: (id: string) => Promise<void>;
create: (device: DeviceRequest) => Promise<void>;
@ -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<Device, "id" | "device_code" | "device_type" | "port_amount" | "status"> {
address: string;
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 {
cacheExpired: () => boolean;
}
export interface SelectState {
value: string;
label: string;
}