From 4bb3c94784a2b8d7ec2e5dfefe3f321a4a0c6cb3 Mon Sep 17 00:00:00 2001 From: Sewmina Date: Thu, 6 Nov 2025 00:25:08 +0530 Subject: [PATCH] jobs --- app/admin/dashboard/page.tsx | 115 +++ app/admin/jobs/add/page.tsx | 663 ++++++++++++++++++ app/admin/jobs/page.tsx | 540 ++++++++++++++ app/admin/page.tsx | 97 +++ app/api/admin/customers/route.ts | 66 ++ app/api/admin/jobs/[id]/actions/route.ts | 36 + app/api/admin/jobs/route.ts | 70 ++ app/api/admin/vehicles/route.ts | 69 ++ .../[vehicleId]/actions/[recordId]/route.ts | 35 + app/api/vehicle/[vehicleId]/records/route.ts | 39 ++ app/components/AdminLogo.tsx | 28 + app/components/Header.tsx | 12 +- app/page.tsx | 7 +- app/vehicle/[vehicleId]/page.tsx | 463 ++++++++++++ db_structure_sql | 44 ++ lib/supabase.ts | 21 + package-lock.json | 118 +++- package.json | 13 +- supabase_config | 0 19 files changed, 2420 insertions(+), 16 deletions(-) create mode 100644 app/admin/dashboard/page.tsx create mode 100644 app/admin/jobs/add/page.tsx create mode 100644 app/admin/jobs/page.tsx create mode 100644 app/admin/page.tsx create mode 100644 app/api/admin/customers/route.ts create mode 100644 app/api/admin/jobs/[id]/actions/route.ts create mode 100644 app/api/admin/jobs/route.ts create mode 100644 app/api/admin/vehicles/route.ts create mode 100644 app/api/vehicle/[vehicleId]/actions/[recordId]/route.ts create mode 100644 app/api/vehicle/[vehicleId]/records/route.ts create mode 100644 app/components/AdminLogo.tsx create mode 100644 app/vehicle/[vehicleId]/page.tsx create mode 100644 db_structure_sql create mode 100644 lib/supabase.ts create mode 100644 supabase_config diff --git a/app/admin/dashboard/page.tsx b/app/admin/dashboard/page.tsx new file mode 100644 index 0000000..692dd1a --- /dev/null +++ b/app/admin/dashboard/page.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import AdminLogo from '../../components/AdminLogo'; + +export default function AdminDashboard() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const router = useRouter(); + + useEffect(() => { + // Check if admin is logged in + const adminLoggedIn = sessionStorage.getItem('adminLoggedIn'); + if (adminLoggedIn !== 'true') { + router.push('/admin'); + return; + } + setIsAuthenticated(true); + }, [router]); + + const handleLogout = () => { + sessionStorage.removeItem('adminLoggedIn'); + router.push('/admin'); + }; + + if (!isAuthenticated) { + return null; + } + + const adminActions = [ + { + id: 'add-job', + title: 'Add New Job', + description: 'Create a new service job for a vehicle', + href: '/admin/jobs/add', + }, + { + id: 'view-jobs', + title: 'View All Jobs', + description: 'Browse and search all service jobs', + href: '/admin/jobs', + }, + // Add more actions here in the future + ]; + + return ( + <> + +
+ {/* Animated background elements */} +
+
+
+
+ +
+
+
+
+
+
+
+

+ Admin Dashboard +

+
+ +
+ +
+

+ Admin Actions +

+
+ {adminActions.map((action) => ( + +
+
+

+ {action.title} +

+
+

+ {action.description} +

+ + ))} +
+
+ +
+ + ← Back to Home + +
+
+
+
+
+ + ); +} diff --git a/app/admin/jobs/add/page.tsx b/app/admin/jobs/add/page.tsx new file mode 100644 index 0000000..28e7544 --- /dev/null +++ b/app/admin/jobs/add/page.tsx @@ -0,0 +1,663 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import AdminLogo from '../../../components/AdminLogo'; + +interface Customer { + id: number; + name: string; + address: string; + tier: number; +} + +interface Vehicle { + id: string; + make: string; + model: string; + capacity: number; + yom: number; + owner: number | null; +} + +export default function AddJobPage() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const router = useRouter(); + + // Form state + const [selectedCustomerId, setSelectedCustomerId] = useState(''); + const [selectedVehicleId, setSelectedVehicleId] = useState(''); + const [metadata, setMetadata] = useState(''); + + // Search state + const [customerSearch, setCustomerSearch] = useState(''); + const [vehicleSearch, setVehicleSearch] = useState(''); + const [showCustomerResults, setShowCustomerResults] = useState(false); + const [showVehicleResults, setShowVehicleResults] = useState(false); + + // Inline creation state + const [showAddCustomer, setShowAddCustomer] = useState(false); + const [showAddVehicle, setShowAddVehicle] = useState(false); + const [newCustomerName, setNewCustomerName] = useState(''); + const [newCustomerAddress, setNewCustomerAddress] = useState(''); + const [newVehicleId, setNewVehicleId] = useState(''); + const [newVehicleMake, setNewVehicleMake] = useState(''); + const [newVehicleModel, setNewVehicleModel] = useState(''); + const [newVehicleCapacity, setNewVehicleCapacity] = useState(''); + const [newVehicleYom, setNewVehicleYom] = useState(''); + + // Mock data - replace with API calls + const [customers, setCustomers] = useState([]); + const [vehicles, setVehicles] = useState([]); + + useEffect(() => { + const adminLoggedIn = sessionStorage.getItem('adminLoggedIn'); + if (adminLoggedIn !== 'true') { + router.push('/admin'); + return; + } + setIsAuthenticated(true); + loadCustomers(); + loadVehicles(); + }, [router]); + + const loadCustomers = async () => { + try { + const response = await fetch('/api/admin/customers'); + if (response.ok) { + const data = await response.json(); + setCustomers(data); + } else { + console.error('Failed to load customers'); + } + } catch (error) { + console.error('Error loading customers:', error); + } + }; + + const loadVehicles = async () => { + try { + const response = await fetch('/api/admin/vehicles'); + if (response.ok) { + const data = await response.json(); + setVehicles(data); + } else { + console.error('Failed to load vehicles'); + } + } catch (error) { + console.error('Error loading vehicles:', error); + } + }; + + const handleAddCustomer = async () => { + if (!newCustomerName.trim()) return; + + try { + const response = await fetch('/api/admin/customers', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: newCustomerName, + address: newCustomerAddress, + tier: 0, + }), + }); + + if (response.ok) { + const result = await response.json(); + const newCustomer = result.customer; + setCustomers([...customers, newCustomer]); + setSelectedCustomerId(newCustomer.id.toString()); + setNewCustomerName(''); + setNewCustomerAddress(''); + setShowAddCustomer(false); + setCustomerSearch(''); + setShowCustomerResults(false); + } else { + const error = await response.json(); + alert(`Failed to create customer: ${error.error || error.details}`); + } + } catch (error) { + console.error('Error creating customer:', error); + alert('Failed to create customer'); + } + }; + + const handleAddVehicle = async () => { + if (!newVehicleId.trim() || !newVehicleMake.trim() || !newVehicleModel.trim()) return; + + try { + const response = await fetch('/api/admin/vehicles', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: newVehicleId.toUpperCase(), + make: newVehicleMake, + model: newVehicleModel, + capacity: parseInt(newVehicleCapacity) || 200, + yom: parseInt(newVehicleYom) || new Date().getFullYear(), + owner: selectedCustomerId ? parseInt(selectedCustomerId) : null, + }), + }); + + if (response.ok) { + const result = await response.json(); + const newVehicle = result.vehicle; + setVehicles([...vehicles, newVehicle]); + setSelectedVehicleId(newVehicle.id); + setNewVehicleId(''); + setNewVehicleMake(''); + setNewVehicleModel(''); + setNewVehicleCapacity(''); + setNewVehicleYom(''); + setShowAddVehicle(false); + setVehicleSearch(''); + setShowVehicleResults(false); + } else { + const error = await response.json(); + alert(`Failed to create vehicle: ${error.error || error.details}`); + } + } catch (error) { + console.error('Error creating vehicle:', error); + alert('Failed to create vehicle'); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!selectedCustomerId) { + alert('Please select a customer'); + return; + } + + if (!selectedVehicleId) { + alert('Please select a vehicle'); + return; + } + + try { + const response = await fetch('/api/admin/jobs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + vehicleId: selectedVehicleId, + metadata: metadata || null, + }), + }); + + if (response.ok) { + const result = await response.json(); + alert('Job created successfully!'); + router.push('/admin/dashboard'); + } else { + const error = await response.json(); + alert(`Failed to create job: ${error.error || error.details}`); + } + } catch (error) { + console.error('Error creating job:', error); + alert('Failed to create job'); + } + }; + + const selectedCustomer = customers.find(c => c.id.toString() === selectedCustomerId); + const selectedVehicle = vehicles.find(v => v.id === selectedVehicleId); + + const filteredCustomers = customers.filter(customer => + customer.name.toLowerCase().includes(customerSearch.toLowerCase()) || + (customer.address && customer.address.toLowerCase().includes(customerSearch.toLowerCase())) + ); + + const filteredVehicles = vehicles + .filter(v => !selectedCustomerId || v.owner === parseInt(selectedCustomerId)) + .filter(vehicle => + vehicle.id.toLowerCase().includes(vehicleSearch.toLowerCase()) || + vehicle.make.toLowerCase().includes(vehicleSearch.toLowerCase()) || + vehicle.model.toLowerCase().includes(vehicleSearch.toLowerCase()) + ); + + // Close dropdowns when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest('.customer-search-container') && !target.closest('.vehicle-search-container')) { + setShowCustomerResults(false); + setShowVehicleResults(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + if (!isAuthenticated) { + return null; + } + + return ( + <> + +
+ {/* Animated background elements */} +
+
+
+
+ +
+
+
+
+
+
+
+

+ Add New Job +

+
+ + ← Back to Dashboard + +
+ +
+ {/* Customer Selection */} +
+
+ + +
+ {selectedCustomer ? ( +
+ + {selectedCustomer.name} {selectedCustomer.address ? `- ${selectedCustomer.address}` : ''} + + +
+ ) : ( + <> + { + setCustomerSearch(e.target.value); + setShowCustomerResults(true); + }} + onFocus={() => setShowCustomerResults(true)} + placeholder="Search customer by name or address..." + className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white placeholder-white/40 focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all" + /> + {showCustomerResults && customerSearch && filteredCustomers.length > 0 && ( +
+ {filteredCustomers.map((customer) => ( + + ))} +
+ )} + {showCustomerResults && customerSearch && filteredCustomers.length === 0 && ( +
+ No customers found +
+ )} + + )} +
+ + {/* Vehicle Selection */} +
+
+ + +
+ {selectedVehicle ? ( +
+ + {selectedVehicle.id} - {selectedVehicle.make} {selectedVehicle.model} + + +
+ ) : ( + <> + { + setVehicleSearch(e.target.value); + setShowVehicleResults(true); + }} + onFocus={() => setShowVehicleResults(true)} + placeholder={selectedCustomerId ? "Search vehicles for selected customer..." : "Search vehicle by number, make, or model..."} + className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white placeholder-white/40 focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all" + disabled={!selectedCustomerId} + /> + {!selectedCustomerId && ( +

+ Please select a customer first +

+ )} + {showVehicleResults && vehicleSearch && filteredVehicles.length > 0 && ( +
+ {filteredVehicles.map((vehicle) => ( + + ))} +
+ )} + {showVehicleResults && vehicleSearch && filteredVehicles.length === 0 && ( +
+ No vehicles found +
+ )} + + )} +
+ + {/* Metadata */} +
+ +