diff --git a/frontend/app/dashboard-layout.ts b/frontend/app/dashboard-layout.ts new file mode 100644 index 00000000..28c723ad --- /dev/null +++ b/frontend/app/dashboard-layout.ts @@ -0,0 +1,166 @@ +// frontend/app/(dashboard)/layout.tsx +import { Sidebar } from '@/components/layout/sidebar'; +import { Topbar } from '@/components/layout/topbar'; + +export default function DashboardLayout({ children }: { children: React.ReactNode }) { +return ( +
+ + +
+
{children}
+
+
+); +} + +// frontend/components/layout/topbar.tsx +'use client'; + +import { useRouter } from 'next/navigation'; +import { LogOut, User } from 'lucide-react'; +import { useAuthStore } from '@/store/auth.store'; + +export function Topbar() { +const router = useRouter(); +const { user, logout } = useAuthStore(); + +const handleLogout = async () => { +await logout(); +router.push('/login'); +}; + +const initials = user +? `${user.firstName[0]}${user.lastName[0]}`.toUpperCase() +: '?'; + +return ( +
+
+ +
+ {/* User info */} +
+
+ {user ? ( + {initials} + ) : ( + + )} +
+ {user && ( +
+

+ {user.firstName} {user.lastName} +

+

{user.role}

+
+ )} +
+ + {/* Divider */} +
+ + {/* Logout */} + +
+
+ +); +} + +// frontend/components/layout/sidebar.tsx +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { clsx } from "clsx"; +import { +LayoutDashboard, +Package, +Users, +Building2, +BarChart3, +Settings, +} from "lucide-react"; + +const navItems = [ +{ href: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, +{ href: "/assets", label: "Assets", icon: Package }, +{ href: "/users", label: "Users", icon: Users }, +{ href: "/departments", label: "Organisation", icon: Building2 }, +{ href: "/reports", label: "Reports", icon: BarChart3 }, +]; + +export function Sidebar() { +const pathname = usePathname(); + +return ( + + +); +} diff --git a/frontend/lib/query/hooks/query.hook.ts b/frontend/lib/query/hooks/query.hook.ts new file mode 100644 index 00000000..17f766d9 --- /dev/null +++ b/frontend/lib/query/hooks/query.hook.ts @@ -0,0 +1,161 @@ +// frontend/lib/query/hooks/useAssets.ts +import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query'; +import { +assetApiClient, +AssetListFilters, +AssetListResponse, +CreateAssetInput, +DepartmentWithCount, +CategoryWithCount, +} from '@/lib/api/assets'; +import { queryKeys } from '../keys'; +import { Asset } from '../types/asset'; +import { ApiError } from '../types'; + +export function useAssets( +filters?: AssetListFilters, +options?: Omit, 'queryKey' | 'queryFn'>, +) { +return useQuery({ +queryKey: queryKeys.assets.list(filters as Record ?? {}), +queryFn: () => assetApiClient.getAssets(filters), +...options, +}); +} + +// ── Departments ────────────────────────────────────────────── + +export function useDepartmentsList() { +return useQuery({ +queryKey: queryKeys.departments.list(), +queryFn: () => assetApiClient.getDepartments(), +}); +} + +export function useCreateDepartment() { +const queryClient = useQueryClient(); +return useMutation<{ id: string; name: string }, ApiError, { name: string; description?: string }>({ +mutationFn: (data) => assetApiClient.createDepartment(data), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: queryKeys.departments.all }); +}, +}); +} + +export function useDeleteDepartment() { +const queryClient = useQueryClient(); +return useMutation({ +mutationFn: (id) => assetApiClient.deleteDepartment(id), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: queryKeys.departments.all }); +}, +}); +} + +// ── Categories ─────────────────────────────────────────────── + +export function useCategories() { +return useQuery({ +queryKey: queryKeys.categories.list(), +queryFn: () => assetApiClient.getCategories(), +}); +} + +export function useCreateCategory() { +const queryClient = useQueryClient(); +return useMutation<{ id: string; name: string }, ApiError, { name: string; description?: string }>({ +mutationFn: (data) => assetApiClient.createCategory(data), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: queryKeys.categories.all }); +}, +}); +} + +export function useDeleteCategory() { +const queryClient = useQueryClient(); +return useMutation({ +mutationFn: (id) => assetApiClient.deleteCategory(id), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: queryKeys.categories.all }); +}, +}); +} + +// ── Assets ─────────────────────────────────────────────────── + +export function useCreateAsset() { +const queryClient = useQueryClient(); +return useMutation({ +mutationFn: (data) => assetApiClient.createAsset(data), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: queryKeys.assets.all }); +}, +}); +} + +export function useUpdateAsset(id: string) { +const queryClient = useQueryClient(); +return useMutation>({ +mutationFn: (data) => assetApiClient.updateAsset(id, data), +onSuccess: (updated) => { +queryClient.setQueryData(queryKeys.assets.detail(id), updated); +queryClient.invalidateQueries({ queryKey: queryKeys.assets.all }); +}, +}); +} + +--- + +// frontend/lib/query/hooks/useReports.ts +import { useQuery } from '@tanstack/react-query'; +import { reportsApiClient, ReportsSummary } from '@/lib/api/reports'; + +export function useReportsSummary() { +return useQuery({ +queryKey: ['reports', 'summary'], +queryFn: () => reportsApiClient.getSummary(), +}); +} + +--- + +// frontend/lib/query/hooks/useUser.ts +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { +usersApiClient, +AppUser, +UserRole, +UpdateProfileInput, +} from "@/lib/api/users"; + +const usersKeys = { +all: ["users"] as const, +list: (search?: string) => [...usersKeys.all, "list", search ?? ""] as const, +}; + +export function useUsersList(search?: string) { +return useQuery({ +queryKey: usersKeys.list(search), +queryFn: () => usersApiClient.getUsers(search), +}); +} + +export function useUpdateUserRole() { +const queryClient = useQueryClient(); +return useMutation({ +mutationFn: ({ id, role }) => usersApiClient.updateRole(id, role), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: usersKeys.all }); +}, +}); +} + +export function useUpdateProfile() { +const queryClient = useQueryClient(); +return useMutation({ +mutationFn: (data) => usersApiClient.updateProfile(data), +onSuccess: () => { +queryClient.invalidateQueries({ queryKey: usersKeys.all }); +}, +}); +}