Tech stack
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js, App Router | ^16.1.7 |
| Language | TypeScript, strict mode | ^5 |
| Styling | Tailwind CSS | v4 |
| Components | shadcn/ui + Radix UI | — |
| Forms | React Hook Form + Zod | ^7.66 / ^4.1.13 |
| Charts | Recharts | ^2.15.3 |
| Icons | lucide-react | ^0.555.0 |
| Tests (unit) | Vitest + @vitest/coverage-v8 | ^3 |
| Tests (e2e) | Playwright | ^1.49.0 |
| Deploy | Vercel | — |
Project structure
Top-level directories
| Directory | Purpose |
|---|---|
src/app/ | Next.js App Router pages and BFF proxy API routes |
src/components/ | Reusable UI components — layout, navigation, charts, and shadcn/ui primitives |
src/contexts/ | React Context providers for global state (user, vehicles, sidebar) |
src/hooks/ | Custom hooks for shared logic (badge counts, chart colors) |
src/Types.ts | All shared TypeScript interfaces and entity types |
middleware.js | Edge middleware for JWT-based route protection |
App Router pages
All dashboard pages live under the(dashboard) route group and are wrapped in a shared layout that mounts the global providers.
| Route | Description |
|---|---|
/ | Login page |
/dashboard | Vehicle grid overview |
/add-vehicle | Add a new vehicle |
/edit-vehicle/[alias] | Edit an existing vehicle |
/vehicle-stats/[alias] | Statistics for a specific vehicle |
/fuel-analysis/[alias] | Fuel consumption analysis per vehicle |
/routes-history | All logged routes |
/refuels-history | All fuel refuel records |
/maintenance-history | Maintenance records with filters |
/upcoming-maintenance | Maintenance due soon or overdue |
/expenses-history | All expense records with filters |
/expenses-summary | Expense breakdown by category |
/upcoming-expenses | Recurring expenses due in the next 30 days |
/profile | User profile and password settings |
/admin-users | User management (admin role required) |
React contexts
All three providers are mounted insrc/app/(dashboard)/layout.tsx and available to every dashboard page.
UserContext
Manages authentication state. Access via
useUser(). Provides user, isLoading, isAdmin, isRoot, isAuthenticated, and refreshUser().VehicleContext
Manages active vehicle list and the currently selected vehicle. Access via
useVehicle(). Provides vehicles, selectedVehicle, setSelectedVehicle, isLoading, and refreshVehicles().SidebarContext
Manages sidebar collapse state. Persists to
localStorage and syncs across browser tabs. Access via useSidebar(). Provides isCollapsed, toggleSidebar, collapseSidebar, and expandSidebar.ThemeContext
Manages light/dark theme. Persists to
localStorage under key km-theme and respects prefers-color-scheme on first visit. Access via useTheme(). Provides theme and toggleTheme().Key hooks
useUpcomingCounts
Fetches badge counts for the navigation sidebar. Calls /api/maintenance/upcoming and /api/expenses/upcoming in parallel on mount, then refreshes every 5 minutes.
0 for any count on a fetch failure — error contains the error message string, or null on success. The hook never throws.
useChartColors
Returns a set of color tokens that adapt to the current light/dark mode. Use this in every Recharts component.
useMediaQuery
Returns a boolean indicating whether a CSS media query matches. Used internally by responsive components.
useApiData
Generic hook for data fetching from /api/* routes with loading and error state.
Mobile-first patterns
KilomeTracker is used primarily on mobile devices. All UI decisions follow these conventions.FilterPanel
UseFilterPanel (src/components/ui/FilterPanel.tsx) on any page with filters. It renders differently per viewport:
- Mobile: a “Filters” button with an active-count badge, opening a Sheet from the bottom.
- Desktop: a Card with a header, filter fields in a grid, and an Apply button.
Card lists vs tables
History pages render two layouts based on the viewport:Skeleton loading states
Always useCardSkeleton while data is loading to prevent layout shifts.