From 068e76d7010e7ca87d1fa5d60d8e3af9e769580a Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 20 Feb 2026 16:00:26 +0700 Subject: [PATCH] 260220:1600 20260220 TASK-BEFE-001 Refactor by ADR-014 #3 --- frontend/app/(admin)/layout.tsx | 14 +- frontend/components/admin/sidebar.tsx | 135 ++++++++++-- frontend/components/common/data-table.tsx | 38 +--- frontend/components/layout/header.tsx | 15 +- frontend/components/layout/sidebar.tsx | 245 ++++++++++++++-------- frontend/components/ui/sheet.tsx | 140 +++++++++++++ 6 files changed, 443 insertions(+), 144 deletions(-) create mode 100644 frontend/components/ui/sheet.tsx diff --git a/frontend/app/(admin)/layout.tsx b/frontend/app/(admin)/layout.tsx index c0de9f1..a3e5199 100644 --- a/frontend/app/(admin)/layout.tsx +++ b/frontend/app/(admin)/layout.tsx @@ -1,4 +1,4 @@ -import { AdminSidebar } from '@/components/admin/sidebar'; +import { AdminSidebar, AdminMobileSidebar } from '@/components/admin/sidebar'; import { auth } from '@/lib/auth'; @@ -16,9 +16,17 @@ export default async function AdminLayout({ children }: { children: React.ReactN } return ( -
+
-
{children}
+
+ {/* Mobile Header for Admin Panel */} +
+ +

LCBP3-DMS Admin

+
+ {/* Main Content */} +
{children}
+
); } diff --git a/frontend/components/admin/sidebar.tsx b/frontend/components/admin/sidebar.tsx index d55adc4..a93116f 100644 --- a/frontend/components/admin/sidebar.tsx +++ b/frontend/components/admin/sidebar.tsx @@ -4,19 +4,7 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useState } from 'react'; import { cn } from '@/lib/utils'; -import { - Users, - Building2, - Settings, - FileText, - Activity, - GitGraph, - Shield, - BookOpen, - FileStack, - ChevronDown, - ChevronRight, -} from 'lucide-react'; +import { Settings, Activity, Shield, FileStack, ChevronDown, ChevronRight } from 'lucide-react'; interface MenuItem { href?: string; @@ -25,7 +13,7 @@ interface MenuItem { children?: { href: string; label: string }[]; } -const menuItems: MenuItem[] = [ +export const menuItems: MenuItem[] = [ { label: 'Access Control', icon: Shield, @@ -169,3 +157,122 @@ export function AdminSidebar() { ); } + +import { Sheet, SheetContent, SheetTrigger, SheetTitle } from '@/components/ui/sheet'; +import { Button } from '@/components/ui/button'; +import { Menu } from 'lucide-react'; + +export function AdminMobileSidebar() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + const [expandedMenus, setExpandedMenus] = useState( + menuItems + .filter((item) => item.children?.some((child) => pathname.startsWith(child.href))) + .map((item) => item.label) + ); + + const toggleMenu = (label: string) => { + setExpandedMenus((prev) => (prev.includes(label) ? prev.filter((l) => l !== label) : [...prev, label])); + }; + + return ( + + + + + + Admin Navigation +
+ Admin Console +
+ +
+ + +
+ setOpen(false)} + className="text-sm text-muted-foreground hover:text-foreground flex items-center gap-2" + > + ← Back to Dashboard + +
+
+
+
+ ); +} diff --git a/frontend/components/common/data-table.tsx b/frontend/components/common/data-table.tsx index b3b3a61..a607fee 100644 --- a/frontend/components/common/data-table.tsx +++ b/frontend/components/common/data-table.tsx @@ -1,4 +1,4 @@ -"use client"; +'use client'; import { ColumnDef, @@ -7,26 +7,16 @@ import { useReactTable, getSortedRowModel, SortingState, -} from "@tanstack/react-table"; -import { useState } from "react"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; +} from '@tanstack/react-table'; +import { useState } from 'react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; interface DataTableProps { columns: ColumnDef[]; data: TData[]; } -export function DataTable({ - columns, - data, -}: DataTableProps) { +export function DataTable({ columns, data }: DataTableProps) { const [sorting, setSorting] = useState([]); const table = useReactTable({ @@ -41,19 +31,14 @@ export function DataTable({ }); return ( -
+
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ))} @@ -62,14 +47,9 @@ export function DataTable({ {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( - + {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )) diff --git a/frontend/components/layout/header.tsx b/frontend/components/layout/header.tsx index 46ab139..81ed120 100644 --- a/frontend/components/layout/header.tsx +++ b/frontend/components/layout/header.tsx @@ -1,16 +1,17 @@ -"use client"; +'use client'; -import { UserMenu } from "./user-menu"; -import { Button } from "@/components/ui/button"; -import { GlobalSearch } from "./global-search"; -import { NotificationsDropdown } from "./notifications-dropdown"; +import { UserMenu } from './user-menu'; +import { GlobalSearch } from './global-search'; +import { NotificationsDropdown } from './notifications-dropdown'; +import { MobileSidebar } from './sidebar'; export function Header() { return (
-

LCBP3-DMS

-
+ +

LCBP3-DMS

+
diff --git a/frontend/components/layout/sidebar.tsx b/frontend/components/layout/sidebar.tsx index 73bcc0f..ffb8130 100644 --- a/frontend/components/layout/sidebar.tsx +++ b/frontend/components/layout/sidebar.tsx @@ -1,8 +1,8 @@ -"use client"; +'use client'; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { cn } from "@/lib/utils"; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { cn } from '@/lib/utils'; import { LayoutDashboard, FileText, @@ -14,10 +14,79 @@ import { Menu, Layers, BookOpen, -} from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useState } from "react"; -import { Can } from "@/components/common/can"; +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { useState } from 'react'; +import { Can } from '@/components/common/can'; + +export const mainNavItems = [ + { + title: 'Dashboard', + href: '/dashboard', + icon: LayoutDashboard, + permission: null, // Everyone can see + }, + { + title: 'Correspondences', + href: '/correspondences', + icon: FileText, + permission: null, + }, + { + title: 'RFAs', + href: '/rfas', + icon: FileCheck, + permission: null, + }, + { + title: 'Drawings', + href: '/drawings', + icon: PenTool, + permission: null, + }, + { + title: 'Circulations', + href: '/circulation', + icon: Layers, // Start with generic icon, maybe update import if needed + permission: null, + }, + { + title: 'Transmittals', + href: '/transmittals', + icon: FileText, + permission: null, + }, + { + title: 'Search', + href: '/search', + icon: Search, + permission: null, + }, + { + title: 'Admin Panel', + href: '/admin', + icon: Shield, + permission: null, + }, + { + title: 'Security', + href: '/admin/access-control/roles', + icon: Shield, + permission: 'system.manage_security', + }, + { + title: 'System Logs', + href: '/admin/monitoring/system-logs/numbering', + icon: Layers, + permission: 'system.view_logs', + }, + { + title: 'Reference Data', + href: '/admin/doc-control/reference', + icon: BookOpen, + permission: 'master_data.view', + }, +]; interface SidebarProps { className?: string; @@ -27,94 +96,21 @@ export function Sidebar({ className }: SidebarProps) { const pathname = usePathname(); const [collapsed, setCollapsed] = useState(false); - const navItems = [ - { - title: "Dashboard", - href: "/dashboard", - icon: LayoutDashboard, - permission: null, // Everyone can see - }, - { - title: "Correspondences", - href: "/correspondences", - icon: FileText, - permission: null, - }, - { - title: "RFAs", - href: "/rfas", - icon: FileCheck, - permission: null, - }, - { - title: "Drawings", - href: "/drawings", - icon: PenTool, - permission: null, - }, - { - title: "Circulations", - href: "/circulation", - icon: Layers, // Start with generic icon, maybe update import if needed - permission: null, - }, - { - title: "Transmittals", - href: "/transmittals", - icon: FileText, - permission: null, - }, - { - title: "Search", - href: "/search", - icon: Search, - permission: null, - }, - { - title: "Admin Panel", - href: "/admin", - icon: Shield, - permission: null, - }, - { - title: "Security", - href: "/admin/security/roles", - icon: Shield, - permission: "system.manage_security", - }, - { - title: "System Logs", - href: "/admin/system-logs/numbering", - icon: Layers, - permission: "system.view_logs", - }, - { - title: "Reference Data", - href: "/admin/reference", - icon: BookOpen, - permission: "master_data.view", - }, - ]; - return (
- {!collapsed && ( - - LCBP3 DMS - - )} + {!collapsed && LCBP3 DMS} @@ -122,7 +118,7 @@ export function Sidebar({ className }: SidebarProps) {
); } + +import { Sheet, SheetContent, SheetTrigger, SheetTitle } from '@/components/ui/sheet'; + +export function MobileSidebar() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + + return ( + + + + + + Mobile Navigation +
+ LCBP3 DMS +
+
+ + +
+ setOpen(false)} + className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground" + > + + Settings + +
+
+
+
+ ); +} diff --git a/frontend/components/ui/sheet.tsx b/frontend/components/ui/sheet.tsx new file mode 100644 index 0000000..a37f17b --- /dev/null +++ b/frontend/components/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +}