Feat/shadcn layout theme overhaul#2
Conversation
…ations - Added recruitment API functions for job postings: get, create, update, and delete. - Implemented hooks for managing job postings and applications using React Query. - Enhanced Admin Settings page with improved layout and accessibility features. - Updated Audit Trail page with better styling and responsive design. - Introduced new types for leave and attendance management. - Enhanced Tailwind CSS configuration with theme tokens and improved dark mode support. - Updated TypeScript configuration for better path resolution. - Integrated shadcn components and improved sidebar navigation experience. - Fixed layout stability issues and ensured consistent dark mode appearance.
There was a problem hiding this comment.
Pull request overview
This PR overhauls the UI layout and theming by adopting shadcn/Radix primitives (sidebar, dialogs, inputs, toasts) and expands the product surface area by implementing Leave, Performance, and Recruitment modules end-to-end (frontend hooks/pages + backend models/routers/services). It also tightens security-related configuration by requiring strong secrets and updating defaults in tests and env examples.
Changes:
- Add shadcn CLI configuration, Tailwind theme tokens, and UI primitives (sidebar, dialog, select, toasts, date picker, etc.), then refactor layout and common UI to use them.
- Implement Leave Requests + Performance Reviews + Recruitment (jobs/applications) across frontend (pages/hooks/types) and backend (models/schemas/services/routers).
- Strengthen operational/security configuration (required secrets in compose/config, cookie flags, password validation) and update tests accordingly.
Reviewed changes
Copilot reviewed 93 out of 94 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| progress.md | Documents recent UI/theming overhaul progress |
| frontend/vite.config.ts | Adds @ alias resolution for Vite |
| frontend/tsconfig.json | Adds TS path mapping for @/* |
| frontend/tsconfig.app.json | Adds TS path mapping for @/* in app build |
| frontend/tailwind.config.ts | Adds shadcn-style theme tokens + animate plugin |
| frontend/src/types/index.ts | Adds list wrappers + Leave/Recruitment/Performance types |
| frontend/src/modules/settings/AuditTrailPage.tsx | Updates styling for dark mode + table overflow handling |
| frontend/src/modules/settings/AdminSettingsPage.tsx | Refactors header/actions + styling adjustments |
| frontend/src/modules/recruitment/hooks.ts | Implements React Query hooks for recruitment module |
| frontend/src/modules/recruitment/api.ts | Adds recruitment API client functions |
| frontend/src/modules/profile/ProfilePage.tsx | Aligns profile cards to theme tokens |
| frontend/src/modules/performance/hooks.ts | Implements React Query hooks for performance module |
| frontend/src/modules/performance/api.ts | Adds performance API client functions |
| frontend/src/modules/performance/PerformancePage.tsx | Replaces placeholder with full Performance UI CRUD |
| frontend/src/modules/payroll/PayrollPage.tsx | Switches to DatePicker + adapts employees response shape |
| frontend/src/modules/leave/hooks.ts | Implements React Query hooks for leave module |
| frontend/src/modules/leave/api.ts | Adds leave request API client functions |
| frontend/src/modules/leave/LeavePage.tsx | Replaces placeholder with full Leave Requests UI |
| frontend/src/modules/employees/api.ts | Updates employees API to return { items, total } |
| frontend/src/modules/employees/EmployeesPage.tsx | Adapts employees list usage + pagination callback |
| frontend/src/modules/employees/EmployeeForm.tsx | Layout tweaks; schedule editor adjustments |
| frontend/src/modules/employees/EmployeeDetailPage.tsx | Adapts employee lookup to new list shape |
| frontend/src/modules/departments/DepartmentsPage.tsx | Adapts employee options to new list shape + pagination callback |
| frontend/src/modules/departments/DepartmentDetailPage.tsx | Adapts employee filtering to new list shape |
| frontend/src/modules/dashboard/DashboardPage.tsx | Adapts employees list shape + dark mode styles |
| frontend/src/main.tsx | Adds ThemeProvider wrapper |
| frontend/src/lib/utils.ts | Adjusts imports (clsx/twMerge) formatting |
| frontend/src/layouts/MainLayout.tsx | Replaces custom sidebar with shadcn sidebar composition |
| frontend/src/index.css | Moves to CSS variables + dark-mode overrides + scrollbar behavior |
| frontend/src/hooks/use-mobile.tsx | Adds mobile breakpoint hook |
| frontend/src/context/ThemeContext.tsx | Adds a (separate) theme context implementation |
| frontend/src/context/NotificationContext.tsx | Replaces custom notifications with Sonner toasts |
| frontend/src/components/ui/tooltip.tsx | Adds Radix tooltip wrapper |
| frontend/src/components/ui/textarea.tsx | Adds shadcn textarea component |
| frontend/src/components/ui/tabs.tsx | Updates tabs to shadcn/Radix implementation |
| frontend/src/components/ui/sonner.tsx | Adds themed Sonner toaster wrapper |
| frontend/src/components/ui/skeleton.tsx | Adds skeleton component |
| frontend/src/components/ui/sheet.tsx | Adds sheet (dialog) component |
| frontend/src/components/ui/separator.tsx | Adds separator component |
| frontend/src/components/ui/select.tsx | Replaces native select with Radix-based select |
| frontend/src/components/ui/popover.tsx | Updates popover to shadcn/Radix implementation |
| frontend/src/components/ui/pagination.tsx | Adds pagination primitives |
| frontend/src/components/ui/modal.tsx | Reimplements Modal via Dialog primitives |
| frontend/src/components/ui/label.tsx | Replaces label with Radix label wrapper |
| frontend/src/components/ui/input.tsx | Replaces input with shadcn input component |
| frontend/src/components/ui/dropdown-menu.tsx | Expands dropdown menu primitives to shadcn/Radix set |
| frontend/src/components/ui/dialog.tsx | Adds dialog primitives |
| frontend/src/components/ui/date-picker.tsx | Adds reusable date picker component |
| frontend/src/components/ui/collapsible.tsx | Adds collapsible primitives |
| frontend/src/components/ui/card.tsx | Updates card primitives to shadcn style |
| frontend/src/components/ui/calendar.tsx | Updates calendar to shadcn DayPicker integration |
| frontend/src/components/ui/button.tsx | Updates button component to shadcn variant system |
| frontend/src/components/ui/badge.tsx | Updates badge component to shadcn variant system |
| frontend/src/components/ui/avatar.tsx | Adds Radix avatar wrapper |
| frontend/src/components/ui/alert.tsx | Updates alert to shadcn variant system |
| frontend/src/components/theme-provider.tsx | Adds shadcn-style theme provider for light/dark/system |
| frontend/src/components/shared/TableControls.tsx | Replaces simple pagination with numbered pagination UI |
| frontend/src/components/shared/PageHeader.tsx | Adds dark-mode styling to header text |
| frontend/src/components/shared/ImageDropInput.tsx | Adds dark-mode styling |
| frontend/src/components/mode-toggle.tsx | Adds theme toggle dropdown |
| frontend/src/components/app-sidebar.tsx | Adds composed AppSidebar using shadcn sidebar primitives |
| frontend/package.json | Adds Radix deps, Sonner, Tailwind animate, etc. |
| frontend/index.html | Adds early theme bootstrap script + color-scheme meta |
| frontend/components.json | Adds shadcn CLI config |
| docker-compose.yml | Requires secret env vars + adds backend healthcheck gating |
| backend/tests/test_leave_requests.py | Adds leave request validation tests |
| backend/tests/test_employees.py | Updates employee list assertions for { items, total } |
| backend/tests/test_auth.py | Updates passwords + employee list shape usage |
| backend/tests/test_audit.py | Updates test passwords |
| backend/tests/test_attendance.py | Updates biometric ingest API key usage |
| backend/tests/conftest.py | Sets required env vars for tests + updates default passwords |
| backend/app/settings/schemas.py | Migrates Pydantic v2 config to ConfigDict |
| backend/app/recruitment/service.py | Implements recruitment service logic |
| backend/app/recruitment/schemas.py | Adds recruitment Pydantic schemas |
| backend/app/recruitment/router.py | Adds recruitment endpoints + audit logging |
| backend/app/recruitment/models.py | Adds recruitment SQLAlchemy models/enums |
| backend/app/performance/service.py | Implements performance review service logic |
| backend/app/performance/schemas.py | Adds performance Pydantic schemas |
| backend/app/performance/router.py | Adds performance endpoints + audit logging |
| backend/app/performance/models.py | Adds performance SQLAlchemy models/enums |
| backend/app/payroll/router.py | Removes N+1 employee lookups via batch loading |
| backend/app/payroll/models.py | Uses UTC-aware timestamp default helper |
| backend/app/main.py | Uses lifespan startup for seeding + imports models for create_all |
| backend/app/leave/service.py | Adds leave request logic + applies admin settings to attendance summary |
| backend/app/leave/schemas.py | Adds leave request schemas and list wrapper |
| backend/app/leave/router.py | Adds leave request endpoints + role restrictions + audit logging |
| backend/app/leave/models.py | Adds leave request model + enums + UTC timestamp defaults |
| backend/app/employees/service.py | Adds pagination/filtering to list employees |
| backend/app/employees/schemas.py | Migrates Pydantic v2 config to ConfigDict |
| backend/app/employees/router.py | Adds query params + returns { items, total } |
| backend/app/config.py | Requires secrets/passwords + validates strength + expands env file search |
| backend/app/auth/schemas.py | Adds password strength validators |
| backend/app/auth/router.py | Sets cookie flags based on environment |
| backend/app/audit/models.py | Uses UTC-aware timestamp default helper |
| backend/.env.example | Updates password guidance in example env |
| .env.example | Adds JWT expiry env var and updates examples |
Comments suppressed due to low confidence (1)
frontend/src/modules/settings/AdminSettingsPage.tsx:185
- Form field captions were changed from semantic
<Label>elements to<p>text, which removes programmatic label association for inputs/selects (hurts screen reader and click-to-focus behavior). Prefer keepingLabelwithhtmlFor+ matchingidon the control (or otherwise ensure accessible labeling).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { useTheme } from '@/components/theme-provider' | ||
|
|
||
| type ToasterProps = React.ComponentProps<typeof Sonner> | ||
|
|
There was a problem hiding this comment.
React is referenced for React.ComponentProps but not imported. With jsx: react-jsx, this will fail type-checking (Cannot find name 'React'). Import React types (e.g., import type React from 'react' / import type { ComponentProps } from 'react') or refactor to avoid the React. namespace.
| import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react' | ||
|
|
||
| type ThemePreference = 'light' | 'dark' | ||
|
|
||
| interface ThemeContextValue { | ||
| theme: ThemePreference | ||
| setTheme: (theme: ThemePreference) => void | ||
| toggleTheme: () => void | ||
| } | ||
|
|
||
| const ThemeContext = createContext<ThemeContextValue | undefined>(undefined) | ||
|
|
||
| const THEME_STORAGE_KEY = 'theme-preference' | ||
|
|
||
| function getInitialTheme(): ThemePreference { | ||
| if (typeof window === 'undefined') { | ||
| return 'light' | ||
| } | ||
|
|
||
| const savedTheme = window.localStorage.getItem(THEME_STORAGE_KEY) | ||
| if (savedTheme === 'light' || savedTheme === 'dark') { | ||
| return savedTheme | ||
| } | ||
|
|
||
| return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' | ||
| } | ||
|
|
||
| function applyTheme(theme: ThemePreference) { | ||
| const root = document.documentElement | ||
| const isDark = theme === 'dark' | ||
| root.classList.toggle('dark', isDark) | ||
| root.style.colorScheme = isDark ? 'dark' : 'light' | ||
| } | ||
|
|
||
| interface ThemeProviderProps { | ||
| children: ReactNode | ||
| } | ||
|
|
||
| export function ThemeProvider({ children }: ThemeProviderProps) { | ||
| const [theme, setThemeState] = useState<ThemePreference>(getInitialTheme) | ||
|
|
||
| useEffect(() => { | ||
| applyTheme(theme) | ||
| window.localStorage.setItem(THEME_STORAGE_KEY, theme) | ||
| }, [theme]) | ||
|
|
||
| const value = useMemo<ThemeContextValue>( | ||
| () => ({ | ||
| theme, | ||
| setTheme: setThemeState, | ||
| toggleTheme: () => setThemeState((currentTheme) => (currentTheme === 'dark' ? 'light' : 'dark')), | ||
| }), | ||
| [theme], | ||
| ) | ||
|
|
||
| return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> | ||
| } | ||
|
|
||
| export function useTheme() { | ||
| const context = useContext(ThemeContext) | ||
| if (!context) { | ||
| throw new Error('useTheme must be used within a ThemeProvider') | ||
| } | ||
| return context | ||
| } No newline at end of file |
There was a problem hiding this comment.
ThemeContext appears to be unused (no imports/usages found in frontend/src) and duplicates the new shadcn-style components/theme-provider. Consider removing this file or wiring the app to use it consistently to avoid confusion and multiple theme storage keys/behaviors.
| "date-fns": "^4.1.0", | ||
| "geist": "^1.7.0", | ||
| "lucide-react": "^0.575.0", | ||
| "next-themes": "^0.4.6", | ||
| "react": "^19.2.0", | ||
| "react-day-picker": "^9.14.0", | ||
| "react-dom": "^19.2.0", | ||
| "react-hook-form": "^7.71.2", | ||
| "react-router-dom": "^7.13.1", | ||
| "recharts": "^3.7.0", | ||
| "sonner": "^2.0.7", | ||
| "tailwind-merge": "^3.5.0", | ||
| "tailwindcss-animate": "^1.0.7", | ||
| "zod": "^4.3.6" |
There was a problem hiding this comment.
next-themes is added as a dependency but doesn’t appear to be used anywhere in frontend/src. If the project is using the local components/theme-provider implementation instead, remove next-themes to reduce install size and avoid unused deps.
| from app.recruitment.service import ( | ||
| create_application, | ||
| create_job_posting, | ||
| delete_job_posting, | ||
| get_application, | ||
| get_job_posting, | ||
| list_applications, | ||
| list_job_postings, | ||
| update_application, | ||
| update_job_posting, | ||
| ) |
There was a problem hiding this comment.
get_application is imported but never used in this router. If you have linting/static checks, this will fail; otherwise it’s still dead code that’s easy to remove.
| import { cn } from "@/lib/utils" | ||
|
|
||
| function Skeleton({ | ||
| className, | ||
| ...props | ||
| }: React.HTMLAttributes<HTMLDivElement>) { | ||
| return ( |
There was a problem hiding this comment.
React is referenced in the props type (React.HTMLAttributes) but this file doesn't import React. In this TS config (jsx: react-jsx), React isn't in scope, so the build will fail. Import the needed type from react (e.g., import type { HTMLAttributes } from 'react') or import React.
| import { createContext, useContext, useEffect, useState } from 'react' | ||
|
|
||
| type Theme = 'dark' | 'light' | 'system' | ||
|
|
||
| type ThemeProviderProps = { | ||
| children: React.ReactNode | ||
| defaultTheme?: Theme | ||
| storageKey?: string | ||
| } |
There was a problem hiding this comment.
React.ReactNode is used in the ThemeProviderProps type but React isn't imported, which will break type-checking under jsx: react-jsx. Import ReactNode (or type React/* as React) from react and use that type instead.
| const AlertTitle = React.forwardRef< | ||
| HTMLParagraphElement, | ||
| React.HTMLAttributes<HTMLHeadingElement> | ||
| >(({ className, ...props }, ref) => ( | ||
| <h5 | ||
| ref={ref} | ||
| className={cn("mb-1 font-medium leading-none tracking-tight", className)} | ||
| {...props} | ||
| /> | ||
| )) | ||
| AlertTitle.displayName = "AlertTitle" | ||
|
|
||
| const AlertDescription = React.forwardRef< | ||
| HTMLParagraphElement, | ||
| React.HTMLAttributes<HTMLParagraphElement> | ||
| >(({ className, ...props }, ref) => ( | ||
| <div | ||
| ref={ref} | ||
| className={cn("text-sm [&_p]:leading-relaxed", className)} | ||
| {...props} | ||
| /> |
There was a problem hiding this comment.
AlertTitle / AlertDescription forwardRef generics don't match the rendered elements (h5 and div). This causes TypeScript ref type errors (e.g., HTMLParagraphElement ref passed to an h5). Update the ref element types and prop types to match the actual tags (or change the tags to match the declared types).
| const storageKey = 'theme-preference' | ||
| const savedTheme = localStorage.getItem(storageKey) | ||
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches | ||
| const shouldUseDark = savedTheme ? savedTheme === 'dark' : prefersDark | ||
| const root = document.documentElement | ||
| root.classList.toggle('dark', shouldUseDark) | ||
| root.style.colorScheme = shouldUseDark ? 'dark' : 'light' |
There was a problem hiding this comment.
The inline theme bootstrap script reads from localStorage key theme-preference, but the app’s ThemeProvider is configured with storageKey="vite-ui-theme". This will cause the initial theme (and subsequent persisted theme) to be inconsistent / flash incorrectly. Align the keys (and supported values, including system if needed) between index.html and the runtime ThemeProvider.
| const storageKey = 'theme-preference' | |
| const savedTheme = localStorage.getItem(storageKey) | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches | |
| const shouldUseDark = savedTheme ? savedTheme === 'dark' : prefersDark | |
| const root = document.documentElement | |
| root.classList.toggle('dark', shouldUseDark) | |
| root.style.colorScheme = shouldUseDark ? 'dark' : 'light' | |
| const storageKey = 'vite-ui-theme' | |
| const savedTheme = localStorage.getItem(storageKey) | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches | |
| const resolvedTheme = | |
| savedTheme === 'light' || savedTheme === 'dark' | |
| ? savedTheme | |
| : prefersDark | |
| ? 'dark' | |
| : 'light' | |
| const root = document.documentElement | |
| root.classList.toggle('dark', resolvedTheme === 'dark') | |
| root.style.colorScheme = resolvedTheme |
No description provided.