feat: crud devices (wip: notif success)
This commit is contained in:
parent
f2bb8eb2bf
commit
e13e236b4a
|
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.0.14",
|
||||
"@tanstack/react-form": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.69.0",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"axios": "^1.8.4",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const Header: React.FC<HeaderProps> = ({
|
|||
toggleSidebar,
|
||||
}) => {
|
||||
return (
|
||||
<header className="p-4 sticky top-0 z-[2]">
|
||||
<header className="p-4 sticky top-0 z-[1]">
|
||||
<div className="flex justify-between items-center">
|
||||
{/* Mobile Sidebar Toggle */}
|
||||
<div className="flex items-center">
|
||||
|
|
@ -84,13 +84,13 @@ const Header: React.FC<HeaderProps> = ({
|
|||
className="dropdown-content bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<Menu.Title>Hi, {user?.name}</Menu.Title>
|
||||
<Dropdown.Item>
|
||||
<NavLink to="#" className="justify-between">
|
||||
<Dropdown.Item anchor={false}>
|
||||
<NavLink to="/" className="justify-between">
|
||||
Profile
|
||||
</NavLink>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item>
|
||||
<NavLink to="#">Settings</NavLink>
|
||||
<Dropdown.Item anchor={false}>
|
||||
<NavLink to="/">Settings</NavLink>
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item anchor={false}>
|
||||
<button onClick={handleLogout}>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const Sidebar: React.FC<SidebarProps> = ({ sidebarOpen, user, handleLogout }) =>
|
|||
>
|
||||
<ReactSVG src={BackboneIcon} title="Backbone" className="w-5 h-5 mr-2" />
|
||||
Backbone
|
||||
<div className="badge badge-neutral">WIP</div>
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
|
|
@ -80,6 +81,7 @@ const Sidebar: React.FC<SidebarProps> = ({ sidebarOpen, user, handleLogout }) =>
|
|||
>
|
||||
<ReactSVG src={FishboneIcon} title="Fishbone" className="w-5 h-5 mr-2" />
|
||||
Fishbone
|
||||
<div className="badge badge-neutral">WIP</div>
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
|
|
@ -102,6 +104,7 @@ const Sidebar: React.FC<SidebarProps> = ({ sidebarOpen, user, handleLogout }) =>
|
|||
>
|
||||
<ReactSVG src={TowerIcon} title="Tower" className="w-5 h-5 mr-2" />
|
||||
Tower
|
||||
<div className="badge badge-neutral">WIP</div>
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
|
|
@ -120,6 +123,7 @@ const Sidebar: React.FC<SidebarProps> = ({ sidebarOpen, user, handleLogout }) =>
|
|||
>
|
||||
<ReactSVG src={UserIcon} title="User" className="w-5 h-5 mr-2" />
|
||||
User
|
||||
<div className="badge badge-neutral">WIP</div>
|
||||
</NavLink>
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const DashboardLayout: React.FC = () => {
|
|||
/>
|
||||
|
||||
{/* Main content area */}
|
||||
<main className="flex-1 p-6 pt-0 z-[1]">
|
||||
<main className="flex-1 p-6 pt-0 z-[0]">
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ interface Callback {
|
|||
export const login = async (credentials: LoginCredentials): Promise<LoginResponse> => {
|
||||
const response = await apiClient.post<ApiResponse<LoginResponse>>('/api/v1/users/login', credentials);
|
||||
|
||||
console.log(response.data);
|
||||
|
||||
if (!response.data.data) {
|
||||
throw new Error('Authentication failed');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// src/features/devices/api/devices.ts
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { DeviceDataTable } from '../types';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiClient, extractApiErrors } from '@/lib/api-client';
|
||||
import { DeviceDataTable, DeviceRequest, DeviceSingleResponse } from '../types';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { ApiResponse } from '@/lib/api-client'; // Pastikan Anda memiliki tipe ApiResponse
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { DataTableDummy } from '../dummy';
|
||||
|
|
@ -28,7 +28,7 @@ export const getDevices = async (): Promise<DeviceDataTable[]> => {
|
|||
throw new Error(response.data.status.description || 'Unknown error');
|
||||
} catch (error) {
|
||||
// Tangani berbagai jenis error
|
||||
if (DataTableDummy.length > 0) {
|
||||
if (DataTableDummy.length > 0 && false) {
|
||||
return DataTableDummy as unknown as DeviceDataTable[];
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +73,71 @@ export const getDevices = async (): Promise<DeviceDataTable[]> => {
|
|||
}
|
||||
};
|
||||
|
||||
export const createDevice = async (deviceData: DeviceRequest): Promise<Pick<ApiResponse<null>, "status">> => {
|
||||
try {
|
||||
const response = await apiClient.post<ApiResponse<null>>('/api/v1/devices', deviceData);
|
||||
return response.data.status as unknown as Pick<ApiResponse<null>, "status">;
|
||||
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const apiError = error.response?.data?.status;
|
||||
throw {
|
||||
code: apiError?.code || 500,
|
||||
message: apiError?.description || 'An unexpected error occurred',
|
||||
errors: apiError?.errors || {}
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDevice = async (id: string, deviceData: Partial<DeviceRequest>): Promise<Pick<ApiResponse<null>, "status">> => {
|
||||
try {
|
||||
const response = await apiClient.put<ApiResponse<null>>(`/api/v1/devices/${id}`, deviceData);
|
||||
return response.data.status as unknown as Pick<ApiResponse<null>, "status">;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const apiError = error.response?.data?.status;
|
||||
throw {
|
||||
code: apiError?.code || 500,
|
||||
message: apiError?.description || 'An unexpected error occurred',
|
||||
errors: apiError?.errors || {}
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getDeviceById = async (id: string): Promise<DeviceSingleResponse> => {
|
||||
console.log(id);
|
||||
|
||||
try {
|
||||
const response = await apiClient.get<ApiResponse<DeviceSingleResponse>>(`/api/v1/devices/${id}`);
|
||||
return response.data.data as DeviceSingleResponse;
|
||||
} catch (error) {
|
||||
const errors = extractApiErrors(error);
|
||||
|
||||
console.log(errors);
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
const apiError = error.response?.data?.status;
|
||||
throw {
|
||||
code: apiError?.code || 500,
|
||||
message: apiError?.description || 'An unexpected error occurred',
|
||||
errors: apiError?.errors || {}
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
*
|
||||
*
|
||||
* ===========================================
|
||||
*/
|
||||
|
||||
export const useDevices = () => {
|
||||
return useQuery<DeviceDataTable[], Error>({
|
||||
queryKey: ['devices'],
|
||||
|
|
@ -81,3 +146,40 @@ export const useDevices = () => {
|
|||
retryDelay: 1000, // Delay 1 detik antara percobaan
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateDevice = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: createDevice,
|
||||
onSuccess: () => {
|
||||
// Invalidate and refetch devices list
|
||||
queryClient.invalidateQueries({ queryKey: ['devices'] });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateDevice = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, deviceData }: { id: string; deviceData: Partial<DeviceRequest> }) =>
|
||||
updateDevice(id, deviceData),
|
||||
onSuccess: () => {
|
||||
// Invalidate and refetch devices list
|
||||
queryClient.invalidateQueries({ queryKey: ['devices'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
// Handle error (you might want to add toast or error handling)
|
||||
console.error('Device update failed', error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeviceById = (id: string) => {
|
||||
return useQuery<DeviceSingleResponse, Error>({
|
||||
queryKey: ['device', id],
|
||||
queryFn: () => getDeviceById(id),
|
||||
enabled: !!id, // Hanya jalankan jika id ada
|
||||
});
|
||||
};
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
// src/features/devices/types.ts
|
||||
export interface DeviceDataTable {
|
||||
id: number;
|
||||
id: string;
|
||||
device_code: string;
|
||||
device_type: string;
|
||||
longitude: number;
|
||||
|
|
@ -11,3 +10,24 @@ export interface DeviceDataTable {
|
|||
status: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface DeviceRequest {
|
||||
device_code: string;
|
||||
device_type: 'OTB' | 'ODP';
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
port_amount: number;
|
||||
status: "maintenance" | "active" | "inactive";
|
||||
}
|
||||
|
||||
export interface DeviceSingleResponse {
|
||||
id: string;
|
||||
device_code: string;
|
||||
device_type: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
port_amount: number;
|
||||
total_used_port: number;
|
||||
status: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import DeviceForm from './DeviceForm';
|
||||
|
||||
export default function CreateDevicePage() {
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-base-100 shadow-md p-4 rounded-lg mb-4">
|
||||
<h1 className="text-2xl font-bold">Create New Device</h1>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<DeviceForm onClose={() => window.history.back()} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
import React from 'react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useCreateDevice, useUpdateDevice } from '@/features/devices/api/devices';
|
||||
import { DeviceRequest, DeviceSingleResponse } from '@/features/devices/types';
|
||||
import Input from '@/components/forms/Input';
|
||||
|
||||
interface DeviceFormProps {
|
||||
initialData?: Partial<DeviceSingleResponse> | null;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const DeviceForm: React.FC<DeviceFormProps> = ({ initialData, onClose }) => {
|
||||
const createDeviceMutation = useCreateDevice();
|
||||
const updateDeviceMutation = useUpdateDevice();
|
||||
|
||||
const defaultValues = {
|
||||
device_code: initialData?.device_code || '',
|
||||
device_type: initialData?.device_type || 'ODP',
|
||||
longitude: initialData?.longitude || 0,
|
||||
latitude: initialData?.latitude || 0,
|
||||
port_amount: initialData?.port_amount || 0,
|
||||
status: initialData?.status || 'active',
|
||||
};
|
||||
|
||||
const form = useForm({
|
||||
defaultValues,
|
||||
// validators: {
|
||||
// onChange: ZodSchema
|
||||
// },
|
||||
onSubmit: async ({ value }) => {
|
||||
try {
|
||||
if (initialData?.id) {
|
||||
// Update existing device
|
||||
await updateDeviceMutation.mutateAsync({
|
||||
id: initialData.id,
|
||||
deviceData: value as Partial<DeviceRequest>
|
||||
});
|
||||
} else {
|
||||
// Create new device
|
||||
console.log(value);
|
||||
|
||||
// Create new device
|
||||
await createDeviceMutation.mutateAsync(value as DeviceRequest);
|
||||
}
|
||||
|
||||
// Close the form or reset
|
||||
onClose?.();
|
||||
} catch (error: any) {
|
||||
|
||||
// Error handling is done in the mutation hooks
|
||||
if (error.errors) {
|
||||
// set error field are {}
|
||||
Object.entries(defaultValues).forEach(([key]) => {
|
||||
form.setFieldMeta(key as keyof typeof defaultValues, (prev) => ({
|
||||
...prev,
|
||||
errorMessage: ''
|
||||
}))
|
||||
})
|
||||
|
||||
Object.entries(error.errors).forEach(([key, value]) => {
|
||||
form.setFieldMeta(key as keyof typeof defaultValues, (prev) => ({
|
||||
...prev,
|
||||
errorMessage: value
|
||||
}))
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-base-100 rounded-lg shadow-md">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
form.handleSubmit();
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<form.Field
|
||||
name="device_code"
|
||||
children={(field) => (
|
||||
<div>
|
||||
<label htmlFor={field.name} className="block mb-2">Device Code</label>
|
||||
<Input
|
||||
id={field.name}
|
||||
type="text"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
className='w-full'
|
||||
bordered
|
||||
color={(field.state.meta as unknown as Record<string, string>).errorMessage ? 'error' : undefined}
|
||||
/>
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage && (
|
||||
<div className="text-error mt-1">
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)} />
|
||||
|
||||
<form.Field
|
||||
name="device_type"
|
||||
children={(field) => (
|
||||
<div>
|
||||
<label htmlFor={field.name} className="block mb-2">Device Type</label>
|
||||
<select
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
onChange={(e) => field.handleChange(e.target.value as "ODP" | "OTB")}
|
||||
className="select select-bordered w-full"
|
||||
>
|
||||
<option value="ODP">ODP</option>
|
||||
<option value="OTB">OTB</option>
|
||||
</select>
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage && (
|
||||
<div className="text-error mt-1">
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<form.Field
|
||||
name="longitude"
|
||||
children={(field) => (
|
||||
<div>
|
||||
<label htmlFor={field.name} className="block mb-2">Longitude</label>
|
||||
<Input
|
||||
id={field.name}
|
||||
type="number"
|
||||
step="0.000001"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(Number(e.target.value))}
|
||||
className="input-bordered w-full"
|
||||
color={field.state.meta.errors.length > 0 ? 'error' : undefined}
|
||||
/>
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage && (
|
||||
<div className="text-error mt-1">
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<form.Field
|
||||
name="latitude"
|
||||
children={(field) => (
|
||||
<div>
|
||||
<label htmlFor={field.name} className="block mb-2">Latitude</label>
|
||||
<Input
|
||||
id={field.name}
|
||||
type="number"
|
||||
step="0.000001"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(Number(e.target.value))}
|
||||
className="input-bordered w-full"
|
||||
color={field.state.meta.errors.length > 0 ? 'error' : undefined}
|
||||
/>
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage && (
|
||||
<div className="text-error mt-1">
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<form.Field
|
||||
name="port_amount"
|
||||
children={(field) => (
|
||||
<div>
|
||||
<label htmlFor={field.name} className="block mb-2">Port Amount</label>
|
||||
<Input
|
||||
id={field.name}
|
||||
type="number"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(Number(e.target.value))}
|
||||
className="input-bordered w-full"
|
||||
color={field.state.meta.errors.length > 0 ? 'error' : undefined}
|
||||
/>
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage && (
|
||||
<div className="text-error mt-1">
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<form.Field
|
||||
name="status"
|
||||
children={(field) => (
|
||||
<div>
|
||||
<label htmlFor={field.name} className="block mb-2">Status</label>
|
||||
<select
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
onChange={(e) => field.handleChange(e.target.value as "active" | "maintenance" | "inactive")}
|
||||
className="select select-bordered w-full"
|
||||
>
|
||||
<option value="active">Active</option>
|
||||
<option value="maintenance">Maintenance</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage && (
|
||||
<div className="text-error mt-1">
|
||||
{(field.state.meta as unknown as Record<string, string>).errorMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-4 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="btn btn-ghost"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
disabled={createDeviceMutation.isPending || updateDeviceMutation.isPending}
|
||||
>
|
||||
{initialData?.id ? 'Update Device' : 'Create Device'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default DeviceForm;
|
||||
|
|
@ -20,19 +20,33 @@ import { Pencil } from "lucide-react";
|
|||
import dayjs from "dayjs";
|
||||
import id from "dayjs/locale/id";
|
||||
import Dropdown from "@/components/actions/Dropdown/Dropdown";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
dayjs.locale(id);
|
||||
|
||||
export default function DevicePage() {
|
||||
const { data: devices, isLoading, isError, error } = useDevices();
|
||||
|
||||
// State untuk sorting dan filtering
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [sorting] = useState<SortingState>([]);
|
||||
const [globalFilter, setGlobalFilter] = useState("");
|
||||
const [pagination, setPagination] = useState({
|
||||
pageIndex: 0,
|
||||
pageSize: 10, // Default 10 data per halaman
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Replace handleCreateDevice with navigation
|
||||
const handleCreateDevice = () => {
|
||||
navigate("/devices/create");
|
||||
};
|
||||
|
||||
// Replace handleEditDevice with navigation
|
||||
const handleEditDevice = (deviceId: string) => {
|
||||
navigate(`/devices/${deviceId}/edit`);
|
||||
};
|
||||
|
||||
const columnHelper = createColumnHelper<DeviceDataTable>();
|
||||
|
||||
const columns = useMemo(
|
||||
|
|
@ -41,33 +55,30 @@ export default function DevicePage() {
|
|||
id: "actions",
|
||||
header: "",
|
||||
cell: ({ row }) => (
|
||||
<Dropdown hover horizontal="right">
|
||||
<Dropdown.Toggle
|
||||
button={false}
|
||||
className="btn btn-ghost btn-sm"
|
||||
<Dropdown hover horizontal="right">
|
||||
<Dropdown.Toggle button={false} className="btn btn-ghost btn-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="w-4 h-4"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="w-4 h-4"
|
||||
>
|
||||
<circle cx={12} cy={12} r={1} />
|
||||
<circle cx={12} cy={5} r={1} />
|
||||
<circle cx={12} cy={19} r={1} />
|
||||
</svg>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="shadow-2xl border p-1 bg-base-100 w-32">
|
||||
<Dropdown.Item>
|
||||
<circle cx={12} cy={12} r={1} />
|
||||
<circle cx={12} cy={5} r={1} />
|
||||
<circle cx={12} cy={19} r={1} />
|
||||
</svg>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="shadow-2xl border p-1 bg-base-100 w-32">
|
||||
<Dropdown.Item onClick={() => handleEditDevice(row.original.id)}>
|
||||
<Pencil className="w-4 h-4 mr-2 text-info" />
|
||||
Edit
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
Edit
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
),
|
||||
size: 10,
|
||||
}),
|
||||
|
|
@ -143,10 +154,12 @@ export default function DevicePage() {
|
|||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="z-10">
|
||||
<div className="bg-base-100 shadow-md p-4 rounded-lg flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold">Devices</h1>
|
||||
<Button color="primary">Add Device</Button>
|
||||
<Button color="primary" onClick={handleCreateDevice}>
|
||||
Add Device
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card className="bg-base-100 shadow-lg mt-8">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import { useParams } from 'react-router';
|
||||
import DeviceForm from './DeviceForm';
|
||||
import { useDeviceById } from '@/features/devices/api/devices';
|
||||
import Loading from '@/components/feedback/Loading';
|
||||
|
||||
export default function EditDevicePage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const { data: device, isLoading } = useDeviceById(id as string);
|
||||
|
||||
if (isLoading) return <Loading />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-base-100 shadow-md p-4 rounded-lg mb-4">
|
||||
<h1 className="text-2xl font-bold">Edit Device</h1>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<DeviceForm
|
||||
initialData={device}
|
||||
onClose={() => window.history.back()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import UsersListPage from '@/pages/users/UsersListPage';
|
|||
import UserFormPage from '@/pages/users/UserFormPage';
|
||||
import NotFoundPage from '@/pages/NotFoundPage';
|
||||
import DevicePage from '@/pages/device/DevicePage';
|
||||
import CreateDevicePage from '@/pages/device/CreateDevicePage';
|
||||
import EditDevicePage from '@/pages/device/EditDevicePage';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
// Public routes
|
||||
|
|
@ -33,6 +35,16 @@ const router = createBrowserRouter([
|
|||
{
|
||||
path: '/devices',
|
||||
element: <DevicePage />,
|
||||
// children: [
|
||||
// ]
|
||||
},
|
||||
{
|
||||
path: '/devices/create',
|
||||
element: <CreateDevicePage />,
|
||||
},
|
||||
{
|
||||
path: '/devices/:id/edit',
|
||||
element: <EditDevicePage />,
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
|
|
|
|||
Loading…
Reference in New Issue