diff --git a/distribution_fe/app/(dashboard)/catalogue/brands/_components/brand-view-sheet.tsx b/distribution_fe/app/(dashboard)/catalogue/brands/_components/brand-view-sheet.tsx new file mode 100644 index 0000000..83cb292 --- /dev/null +++ b/distribution_fe/app/(dashboard)/catalogue/brands/_components/brand-view-sheet.tsx @@ -0,0 +1,48 @@ +'use client' + +import { Badge } from '@/components/ui/badge' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import type { BrandResponse } from '@/types/api' + +const fmtDate = (v: string) => + new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) + +interface BrandViewSheetProps { + open: boolean + onClose: () => void + brand: BrandResponse | null +} + +function Row({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value} +
+ ) +} + +export function BrandViewSheet({ open, onClose, brand }: BrandViewSheetProps) { + if (!brand) return null + + return ( + !v && onClose()}> + + + Brand Details + + +
+ + {brand.isActive ? 'Active' : 'Inactive'}} /> + +
+
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/catalogue/brands/_components/brands-client.tsx b/distribution_fe/app/(dashboard)/catalogue/brands/_components/brands-client.tsx index eb2a987..7328f60 100644 --- a/distribution_fe/app/(dashboard)/catalogue/brands/_components/brands-client.tsx +++ b/distribution_fe/app/(dashboard)/catalogue/brands/_components/brands-client.tsx @@ -9,27 +9,29 @@ import type { TableConfig } from '@/components/shared/data-table' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { toast } from '@/components/ui/toast' -import { brands as brandsApi, getApiError, getPaginatedItems } from '@/lib/api/index' +import { brands as brandsApi, getApiError } from '@/lib/api/index' import type { BrandResponse } from '@/types/api' import { BrandSheet } from './brand-sheet' +import { BrandViewSheet } from './brand-view-sheet' export function BrandsClient() { const [data, setData] = useState([]) const [loading, setLoading] = useState(true) const [total, setTotal] = useState(0) - const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(20) const [sheetOpen, setSheetOpen] = useState(false) const [editing, setEditing] = useState(null) + const [viewOpen, setViewOpen] = useState(false) + const [viewing, setViewing] = useState(null) - const fetchData = useCallback(async (p: number, ps: number) => { + const fetchData = useCallback(async () => { setLoading(true) try { - const res = await brandsApi.list({ page: p, pageSize: ps }) - setData(getPaginatedItems(res)) - setTotal(res.total) + const items = await brandsApi.list() + setData(items) + setTotal(items.length) } catch (err) { toast.add({ title: 'Failed to load brands', description: getApiError(err), type: 'error' }) } finally { @@ -38,8 +40,8 @@ export function BrandsClient() { }, []) useEffect(() => { - fetchData(page, pageSize) - }, [fetchData, page, pageSize]) + fetchData() + }, [fetchData]) function openCreate() { setEditing(null) @@ -51,9 +53,23 @@ export function BrandsClient() { setSheetOpen(true) } + function openView(brand: BrandResponse) { + setViewing(brand) + setViewOpen(true) + } + + async function handleDelete(brand: BrandResponse) { + try { + await brandsApi.remove(brand.id) + toast.add({ title: 'Brand deactivated', type: 'success' }) + fetchData() + } catch (err) { + toast.add({ title: 'Failed to deactivate brand', description: getApiError(err), type: 'error' }) + } + } + const tableConfig = useMemo>(() => ({ rowKey: 'id', - serverSide: true, stickyHeader: true, emptyMessage: 'No brands yet. Add one to get started.', pagination: { enabled: true, pageSize, pageSizeOptions: [10, 20, 50] }, @@ -90,14 +106,25 @@ export function BrandsClient() { ], rowActions: { actions: () => [ + { + action: 'view', + label: 'View brand', + onClick: openView, + }, { action: 'edit', label: 'Edit brand', onClick: openEdit, }, + { + action: 'cancel', + label: 'Delete brand', + onClick: handleDelete, + visible: (target) => target.isActive, + }, ], }, - }), [pageSize, openEdit]) + }), [pageSize]) return ( <> @@ -120,7 +147,6 @@ export function BrandsClient() { loading={loading} totalCount={total} onServerSideChange={({ page: p }) => { - setPage(p.page) setPageSize(p.pageSize) }} /> @@ -129,7 +155,13 @@ export function BrandsClient() { open={sheetOpen} onClose={() => setSheetOpen(false)} brand={editing} - onSuccess={() => fetchData(page, pageSize)} + onSuccess={() => fetchData()} + /> + + setViewOpen(false)} + brand={viewing} /> ) diff --git a/distribution_fe/lib/api/catalogue.ts b/distribution_fe/lib/api/catalogue.ts index 28a8455..b2a598b 100644 --- a/distribution_fe/lib/api/catalogue.ts +++ b/distribution_fe/lib/api/catalogue.ts @@ -74,6 +74,9 @@ export const categories = { update: (id: string, body: Partial) => api.put(`/categories/${id}`, body).then(r => r.data), + remove: (id: string) => + api.delete(`/categories/${id}`), + createSubCategory: (categoryId: string, body: SubCategoryBody) => api.post(`/categories/${categoryId}/subcategories`, body).then(r => r.data), @@ -91,9 +94,11 @@ export interface BrandBody { isActive?: boolean } +// GET /brands takes no query params and returns a plain array — no server-side +// filtering/pagination exists on the backend. export const brands = { - list: (params?: PageParams) => - api.get>('/brands', { params }).then(r => r.data), + list: () => + api.get('/brands').then(r => r.data), get: (id: string) => api.get(`/brands/${id}`).then(r => r.data), @@ -103,6 +108,9 @@ export const brands = { update: (id: string, body: Partial) => api.put(`/brands/${id}`, body).then(r => r.data), + + remove: (id: string) => + api.delete(`/brands/${id}`), } // ─── UOMs ─────────────────────────────────────────────────────────────────────