100kmph/app/admin/jobs/add/page.tsx
2025-11-06 01:24:38 +05:30

696 lines
30 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import AdminLogo from '../../../components/AdminLogo';
import Notification from '../../../components/Notification';
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<string>('');
const [selectedVehicleId, setSelectedVehicleId] = useState<string>('');
const [metadata, setMetadata] = useState('');
const [mileage, setMileage] = 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<Customer[]>([]);
const [vehicles, setVehicles] = useState<Vehicle[]>([]);
// Notification state
const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' | 'info' } | null>(null);
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);
setNotification({ message: 'Customer created successfully!', type: 'success' });
} else {
const error = await response.json();
setNotification({ message: `Failed to create customer: ${error.error || error.details}`, type: 'error' });
}
} catch (error) {
console.error('Error creating customer:', error);
setNotification({ message: 'Failed to create customer', type: 'error' });
}
};
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);
setNotification({ message: 'Vehicle created successfully!', type: 'success' });
} else {
const error = await response.json();
setNotification({ message: `Failed to create vehicle: ${error.error || error.details}`, type: 'error' });
}
} catch (error) {
console.error('Error creating vehicle:', error);
setNotification({ message: 'Failed to create vehicle', type: 'error' });
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedCustomerId) {
setNotification({ message: 'Please select a customer', type: 'error' });
return;
}
if (!selectedVehicleId) {
setNotification({ message: 'Please select a vehicle', type: 'error' });
return;
}
try {
const response = await fetch('/api/admin/jobs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
vehicleId: selectedVehicleId,
metadata: metadata || null,
mileage: mileage ? parseInt(mileage) : null,
}),
});
if (response.ok) {
const result = await response.json();
setNotification({ message: 'Job created successfully!', type: 'success' });
setTimeout(() => {
router.push('/admin/dashboard');
}, 1500);
} else {
const error = await response.json();
setNotification({ message: `Failed to create job: ${error.error || error.details}`, type: 'error' });
}
} catch (error) {
console.error('Error creating job:', error);
setNotification({ message: 'Failed to create job', type: 'error' });
}
};
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 (
<>
<AdminLogo />
<div className="relative min-h-screen bg-black tech-grid scanlines">
{/* Animated background elements */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute top-1/4 left-1/4 h-96 w-96 rounded-full bg-white/5 blur-3xl animate-pulse"></div>
<div className="absolute bottom-1/4 right-1/4 h-96 w-96 rounded-full bg-white/5 blur-3xl animate-pulse delay-1000"></div>
</div>
<div className="relative mx-auto max-w-4xl px-4 py-12">
<div className="relative rounded-lg border-2 border-white/30 bg-black/80 backdrop-blur-sm p-8 neon-border-glow">
<div className="absolute inset-0 rounded-lg bg-gradient-to-br from-white/5 to-transparent"></div>
<div className="relative">
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-white animate-pulse"></div>
<h1 className="text-3xl font-bold text-white uppercase tracking-wider neon-glow">
Add New Job
</h1>
</div>
<Link
href="/admin/dashboard"
className="text-sm font-medium text-white/60 hover:text-white transition-colors font-mono uppercase tracking-wider border border-white/20 px-3 py-1 rounded hover:border-white/40 hover:bg-white/10"
>
Back to Dashboard
</Link>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Customer Selection */}
<div className="relative customer-search-container">
<div className="mb-2 flex items-center justify-between">
<label className="block text-sm font-medium text-white uppercase tracking-wider">
Customer <span className="text-red-500">*</span>
</label>
<button
type="button"
onClick={() => {
setNewCustomerName(customerSearch);
setShowAddCustomer(true);
}}
className="text-xs font-mono uppercase tracking-wider border border-cyan-500/50 bg-cyan-500/10 px-2 py-1 rounded text-cyan-400 transition-all hover:border-cyan-500 hover:bg-cyan-500/20 hover:text-cyan-300 hover:shadow-[0_0_10px_rgba(0,191,255,0.4)]"
>
+ Add New
</button>
</div>
{selectedCustomer ? (
<div className="mb-2 flex items-center justify-between rounded-lg border-2 border-white/30 bg-black/50 px-4 py-2">
<span className="text-white font-mono">
{selectedCustomer.name} {selectedCustomer.address ? `- ${selectedCustomer.address}` : ''}
</span>
<button
type="button"
onClick={() => {
setSelectedCustomerId('');
setSelectedVehicleId('');
setCustomerSearch('');
}}
className="text-white/60 hover:text-white"
>
</button>
</div>
) : (
<>
<input
type="text"
value={customerSearch}
onChange={(e) => {
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 && (
<div className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-lg border-2 border-white/20 bg-black/95 backdrop-blur-sm">
{filteredCustomers.map((customer) => (
<button
key={customer.id}
type="button"
onClick={() => {
setSelectedCustomerId(customer.id.toString());
setSelectedVehicleId('');
setCustomerSearch('');
setShowCustomerResults(false);
}}
className="w-full px-4 py-3 text-left text-white hover:bg-white/10 font-mono transition-colors border-b border-white/10 last:border-b-0"
>
<div className="font-semibold">{customer.name}</div>
{customer.address && (
<div className="text-sm text-white/60">{customer.address}</div>
)}
</button>
))}
</div>
)}
{showCustomerResults && customerSearch && filteredCustomers.length === 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg border-2 border-white/20 bg-black/95 backdrop-blur-sm px-4 py-3 text-white/60 font-mono text-sm">
No customers found
</div>
)}
</>
)}
</div>
{/* Vehicle Selection */}
<div className="relative vehicle-search-container">
<div className="mb-2 flex items-center justify-between">
<label className="block text-sm font-medium text-white uppercase tracking-wider">
Vehicle <span className="text-red-500">*</span>
</label>
<button
type="button"
onClick={() => {
setNewVehicleId(vehicleSearch);
setShowAddVehicle(true);
}}
className="text-xs font-mono uppercase tracking-wider border border-cyan-500/50 bg-cyan-500/10 px-2 py-1 rounded text-cyan-400 transition-all hover:border-cyan-500 hover:bg-cyan-500/20 hover:text-cyan-300 hover:shadow-[0_0_10px_rgba(0,191,255,0.4)]"
>
+ Add New
</button>
</div>
{selectedVehicle ? (
<div className="mb-2 flex items-center justify-between rounded-lg border-2 border-white/30 bg-black/50 px-4 py-2">
<span className="text-white font-mono">
{selectedVehicle.id} - {selectedVehicle.make} {selectedVehicle.model}
</span>
<button
type="button"
onClick={() => {
setSelectedVehicleId('');
setVehicleSearch('');
}}
className="text-white/60 hover:text-white"
>
</button>
</div>
) : (
<>
<input
type="text"
value={vehicleSearch}
onChange={(e) => {
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 && (
<p className="mt-1 text-xs text-white/60 font-mono">
Please select a customer first
</p>
)}
{showVehicleResults && vehicleSearch && filteredVehicles.length > 0 && (
<div className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-lg border-2 border-white/20 bg-black/95 backdrop-blur-sm">
{filteredVehicles.map((vehicle) => (
<button
key={vehicle.id}
type="button"
onClick={() => {
setSelectedVehicleId(vehicle.id);
setVehicleSearch('');
setShowVehicleResults(false);
}}
className="w-full px-4 py-3 text-left text-white hover:bg-white/10 font-mono transition-colors border-b border-white/10 last:border-b-0"
>
<div className="font-semibold">{vehicle.id}</div>
<div className="text-sm text-white/60">
{vehicle.make} {vehicle.model} {vehicle.capacity ? `(${vehicle.capacity}cc)` : ''}
</div>
</button>
))}
</div>
)}
{showVehicleResults && vehicleSearch && filteredVehicles.length === 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg border-2 border-white/20 bg-black/95 backdrop-blur-sm px-4 py-3 text-white/60 font-mono text-sm">
No vehicles found
</div>
)}
</>
)}
</div>
{/* Mileage */}
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Mileage (km)
</label>
<input
type="number"
value={mileage}
onChange={(e) => setMileage(e.target.value)}
placeholder="Enter vehicle mileage..."
min="0"
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"
/>
</div>
{/* Metadata */}
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Metadata / Notes
</label>
<textarea
value={metadata}
onChange={(e) => setMetadata(e.target.value)}
rows={4}
placeholder="Additional notes or metadata..."
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"
/>
</div>
<div className="flex gap-4 pt-4">
<Link
href="/admin/dashboard"
className="flex-1 rounded-lg border-2 border-white/30 bg-black/50 px-6 py-3 text-center font-medium uppercase tracking-wider text-white transition-all hover:bg-white/10 hover:border-white/50 hover:shadow-[0_0_15px_rgba(255,255,255,0.3)]"
>
Cancel
</Link>
<button
type="submit"
className="flex-1 rounded-lg border-2 border-white/30 bg-white/5 px-6 py-3 font-bold uppercase tracking-wider text-white transition-all hover:bg-white/10 hover:border-white/50 hover:text-gray-200 hover:shadow-[0_0_20px_rgba(255,255,255,0.3)] focus:outline-none focus:ring-2 focus:ring-white/30"
>
Create Job
</button>
</div>
</form>
</div>
</div>
</div>
{/* Add Customer Modal */}
{showAddCustomer && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
<div className="relative w-full max-w-md rounded-lg border-2 border-white/30 bg-black/95 p-6 neon-border-glow">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-bold text-white uppercase tracking-wider">
Add New Customer
</h2>
<button
onClick={() => {
setShowAddCustomer(false);
setNewCustomerName('');
setNewCustomerAddress('');
}}
className="text-white/60 hover:text-white"
>
</button>
</div>
<div className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Name <span className="text-red-500">*</span>
</label>
<input
type="text"
value={newCustomerName}
onChange={(e) => setNewCustomerName(e.target.value)}
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Address
</label>
<input
type="text"
value={newCustomerAddress}
onChange={(e) => setNewCustomerAddress(e.target.value)}
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
/>
</div>
<div className="flex gap-4">
<button
type="button"
onClick={() => {
setShowAddCustomer(false);
setNewCustomerName('');
setNewCustomerAddress('');
}}
className="flex-1 rounded-lg border-2 border-white/30 bg-black/50 px-4 py-2 text-white transition-all hover:bg-white/10"
>
Cancel
</button>
<button
type="button"
onClick={handleAddCustomer}
className="flex-1 rounded-lg border-2 border-white/30 bg-white/5 px-4 py-2 font-bold text-white transition-all hover:bg-white/10"
>
Add Customer
</button>
</div>
</div>
</div>
</div>
)}
{/* Add Vehicle Modal */}
{showAddVehicle && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
<div className="relative w-full max-w-md rounded-lg border-2 border-white/30 bg-black/95 p-6 neon-border-glow">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-bold text-white uppercase tracking-wider">
Add New Vehicle
</h2>
<button
onClick={() => {
setShowAddVehicle(false);
setNewVehicleId('');
setNewVehicleMake('');
setNewVehicleModel('');
setNewVehicleCapacity('');
setNewVehicleYom('');
}}
className="text-white/60 hover:text-white"
>
</button>
</div>
<div className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Vehicle Number <span className="text-red-500">*</span>
</label>
<input
type="text"
value={newVehicleId}
onChange={(e) => setNewVehicleId(e.target.value.toUpperCase())}
placeholder="ABC-1234"
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Make <span className="text-red-500">*</span>
</label>
<input
type="text"
value={newVehicleMake}
onChange={(e) => setNewVehicleMake(e.target.value)}
placeholder="Honda"
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Model <span className="text-red-500">*</span>
</label>
<input
type="text"
value={newVehicleModel}
onChange={(e) => setNewVehicleModel(e.target.value)}
placeholder="Civic"
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
required
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Capacity (cc)
</label>
<input
type="number"
value={newVehicleCapacity}
onChange={(e) => setNewVehicleCapacity(e.target.value)}
placeholder="200"
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-white uppercase tracking-wider">
Year of Manufacture
</label>
<input
type="number"
value={newVehicleYom}
onChange={(e) => setNewVehicleYom(e.target.value)}
placeholder={new Date().getFullYear().toString()}
className="w-full rounded-lg border-2 border-white/20 bg-black/50 px-4 py-3 font-mono text-white focus:border-white/50 focus:outline-none focus:ring-2 focus:ring-white/30 neon-border transition-all"
/>
</div>
</div>
{selectedCustomerId && (
<div className="rounded-lg border border-white/20 bg-white/5 p-3">
<p className="text-xs text-white/60 font-mono">
Vehicle will be linked to selected customer
</p>
</div>
)}
<div className="flex gap-4">
<button
type="button"
onClick={() => {
setShowAddVehicle(false);
setNewVehicleId('');
setNewVehicleMake('');
setNewVehicleModel('');
setNewVehicleCapacity('');
setNewVehicleYom('');
}}
className="flex-1 rounded-lg border-2 border-white/30 bg-black/50 px-4 py-2 text-white transition-all hover:bg-white/10"
>
Cancel
</button>
<button
type="button"
onClick={handleAddVehicle}
className="flex-1 rounded-lg border-2 border-white/30 bg-white/5 px-4 py-2 font-bold text-white transition-all hover:bg-white/10"
>
Add Vehicle
</button>
</div>
</div>
</div>
</div>
)}
{notification && (
<Notification
message={notification.message}
type={notification.type}
onClose={() => setNotification(null)}
/>
)}
</>
);
}