feat: add GPS tracking with Traccar integration
- Add GPS module with Traccar client service for device management - Add driver enrollment flow with QR code generation - Add real-time location tracking on driver profiles - Add GPS settings configuration in admin tools - Add Auth0 OpenID Connect setup script for Traccar - Add deployment configs for production server - Update nginx configs for SSL on GPS port 5055 - Add timezone setting support - Various UI improvements and bug fixes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,7 @@ export function Layout({ children }: LayoutProps) {
|
||||
// Admin dropdown items (nested under Admin)
|
||||
const adminItems = [
|
||||
{ name: 'Users', href: '/users', icon: UserCog },
|
||||
{ name: 'GPS Tracking', href: '/gps-tracking', icon: Radio },
|
||||
{ name: 'Admin Tools', href: '/admin-tools', icon: Settings },
|
||||
];
|
||||
|
||||
@@ -89,8 +90,6 @@ export function Layout({ children }: LayoutProps) {
|
||||
if (item.driverOnly) return isDriverRole;
|
||||
// Coordinator-only items hidden from drivers
|
||||
if (item.coordinatorOnly && isDriverRole) return false;
|
||||
// Always show items
|
||||
if (item.alwaysShow) return true;
|
||||
// Permission-based items
|
||||
if (item.requireRead) {
|
||||
return ability.can(Action.Read, item.requireRead);
|
||||
|
||||
@@ -3,25 +3,34 @@ import { Loader2 } from 'lucide-react';
|
||||
interface LoadingProps {
|
||||
message?: string;
|
||||
fullPage?: boolean;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
export function Loading({ message = 'Loading...', fullPage = false }: LoadingProps) {
|
||||
export function Loading({ message = 'Loading...', fullPage = false, size = 'medium' }: LoadingProps) {
|
||||
const sizeClasses = {
|
||||
small: { icon: 'h-5 w-5', text: 'text-sm', padding: 'py-4' },
|
||||
medium: { icon: 'h-8 w-8', text: 'text-base', padding: 'py-12' },
|
||||
large: { icon: 'h-12 w-12', text: 'text-lg', padding: 'py-16' },
|
||||
};
|
||||
|
||||
const { icon, text, padding } = sizeClasses[size];
|
||||
|
||||
if (fullPage) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-muted">
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-12 w-12 text-primary animate-spin mx-auto mb-4" />
|
||||
<p className="text-muted-foreground text-lg">{message}</p>
|
||||
<Loader2 className={`${icon} text-primary animate-spin mx-auto mb-4`} />
|
||||
<p className={`text-muted-foreground ${text}`}>{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className={`flex items-center justify-center ${padding}`}>
|
||||
<div className="text-center">
|
||||
<Loader2 className="h-8 w-8 text-primary animate-spin mx-auto mb-3" />
|
||||
<p className="text-muted-foreground">{message}</p>
|
||||
<Loader2 className={`${icon} text-primary animate-spin mx-auto mb-3`} />
|
||||
<p className={`text-muted-foreground ${text}`}>{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Eye,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Globe,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
usePdfSettings,
|
||||
@@ -38,7 +39,7 @@ export function PdfSettingsSection() {
|
||||
});
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const { register, handleSubmit, watch, reset } = useForm<UpdatePdfSettingsDto>();
|
||||
const { register, handleSubmit, watch, reset, setValue } = useForm<UpdatePdfSettingsDto>();
|
||||
|
||||
const accentColor = watch('accentColor');
|
||||
|
||||
@@ -60,6 +61,7 @@ export function PdfSettingsSection() {
|
||||
showTimestamp: settings.showTimestamp,
|
||||
showAppUrl: settings.showAppUrl,
|
||||
pageSize: settings.pageSize,
|
||||
timezone: settings.timezone,
|
||||
showFlightInfo: settings.showFlightInfo,
|
||||
showDriverNames: settings.showDriverNames,
|
||||
showVehicleNames: settings.showVehicleNames,
|
||||
@@ -350,7 +352,8 @@ export function PdfSettingsSection() {
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
{...register('accentColor')}
|
||||
value={accentColor || '#2c3e50'}
|
||||
onChange={(e) => setValue('accentColor', e.target.value)}
|
||||
className="h-10 w-20 border border-input rounded cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
@@ -554,6 +557,39 @@ export function PdfSettingsSection() {
|
||||
<option value={PageSize.A4}>A4 (210mm x 297mm)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
<Globe className="h-4 w-4 inline mr-1" />
|
||||
System Timezone
|
||||
</label>
|
||||
<select
|
||||
{...register('timezone')}
|
||||
className="w-full px-3 py-2 bg-background text-foreground border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
>
|
||||
<optgroup label="US Timezones">
|
||||
<option value="America/New_York">Eastern Time (ET)</option>
|
||||
<option value="America/Chicago">Central Time (CT)</option>
|
||||
<option value="America/Denver">Mountain Time (MT)</option>
|
||||
<option value="America/Phoenix">Arizona (no DST)</option>
|
||||
<option value="America/Los_Angeles">Pacific Time (PT)</option>
|
||||
<option value="America/Anchorage">Alaska Time (AKT)</option>
|
||||
<option value="Pacific/Honolulu">Hawaii Time (HT)</option>
|
||||
</optgroup>
|
||||
<optgroup label="International">
|
||||
<option value="UTC">UTC (Coordinated Universal Time)</option>
|
||||
<option value="Europe/London">London (GMT/BST)</option>
|
||||
<option value="Europe/Paris">Paris (CET/CEST)</option>
|
||||
<option value="Europe/Berlin">Berlin (CET/CEST)</option>
|
||||
<option value="Asia/Tokyo">Tokyo (JST)</option>
|
||||
<option value="Asia/Shanghai">Shanghai (CST)</option>
|
||||
<option value="Australia/Sydney">Sydney (AEST/AEDT)</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
All times in correspondence and exports will use this timezone
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user