From 00c4a30f6ba11da7a4d609af0019a33957ef4809 Mon Sep 17 00:00:00 2001 From: Sewmina Date: Thu, 6 Nov 2025 01:24:38 +0530 Subject: [PATCH] jobs modify --- app/admin/jobs/[id]/edit/page.tsx | 615 ++++++++++++++++++ app/admin/jobs/add/page.tsx | 54 +- app/admin/jobs/page.tsx | 197 +++--- .../jobs/[id]/actions/[actionId]/route.ts | 78 +++ app/api/admin/jobs/[id]/actions/route.ts | 42 ++ app/api/admin/jobs/[id]/route.ts | 80 +++ app/api/admin/jobs/route.ts | 3 +- app/components/Notification.tsx | 82 +++ app/vehicle/[vehicleId]/page.tsx | 151 ++--- 9 files changed, 1125 insertions(+), 177 deletions(-) create mode 100644 app/admin/jobs/[id]/edit/page.tsx create mode 100644 app/api/admin/jobs/[id]/actions/[actionId]/route.ts create mode 100644 app/api/admin/jobs/[id]/route.ts create mode 100644 app/components/Notification.tsx diff --git a/app/admin/jobs/[id]/edit/page.tsx b/app/admin/jobs/[id]/edit/page.tsx new file mode 100644 index 0000000..fe4b23b --- /dev/null +++ b/app/admin/jobs/[id]/edit/page.tsx @@ -0,0 +1,615 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import Link from 'next/link'; +import AdminLogo from '../../../../components/AdminLogo'; +import Notification from '../../../../components/Notification'; + +interface JobAction { + id: number; + action_type: string; + part_type: string | null; + part_id: string | null; + custom_price: number; + labour_fee: number; + created_at: string; + service_record: number; +} + +interface Vehicle { + id: string; + make: string; + model: string; + capacity: number | null; + yom: number | null; + owner: number | null; +} + +interface ServiceRecord { + id: number; + created_at: string; + vehicle: string; + metadata: string | null; + status: number; + Vehicles?: Vehicle; +} + +export default function EditJobPage() { + const router = useRouter(); + const params = useParams(); + const jobId = params?.id as string; + + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [serviceRecord, setServiceRecord] = useState(null); + const [jobActions, setJobActions] = useState([]); + + // Form state + const [status, setStatus] = useState(0); + const [metadata, setMetadata] = useState(''); + const [mileage, setMileage] = useState(''); + const [showAddAction, setShowAddAction] = useState(false); + const [editingAction, setEditingAction] = useState(null); + + // Notification state + const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' | 'info' } | null>(null); + + // New action form state + const [newActionType, setNewActionType] = useState(''); + const [newPartType, setNewPartType] = useState(''); + const [newPartId, setNewPartId] = useState(''); + const [newCustomPrice, setNewCustomPrice] = useState(''); + const [newLabourFee, setNewLabourFee] = useState(''); + + useEffect(() => { + const adminLoggedIn = sessionStorage.getItem('adminLoggedIn'); + if (adminLoggedIn !== 'true') { + router.push('/admin'); + return; + } + setIsAuthenticated(true); + if (jobId) { + loadJobData(); + } + }, [jobId, router]); + + const loadJobData = async () => { + try { + setLoading(true); + const [recordResponse, actionsResponse] = await Promise.all([ + fetch(`/api/admin/jobs/${jobId}`), + fetch(`/api/admin/jobs/${jobId}/actions`) + ]); + + if (recordResponse.ok) { + const recordData = await recordResponse.json(); + setServiceRecord(recordData); + setStatus(recordData.status); + setMetadata(recordData.metadata || ''); + setMileage(recordData.mileage ? recordData.mileage.toString() : ''); + } + + if (actionsResponse.ok) { + const actionsData = await actionsResponse.json(); + setJobActions(actionsData); + } + } catch (error) { + console.error('Error loading job data:', error); + } finally { + setLoading(false); + } + }; + + const handleSaveServiceRecord = async () => { + try { + setSaving(true); + const response = await fetch(`/api/admin/jobs/${jobId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + status, + metadata: metadata || null, + mileage: mileage ? parseInt(mileage) : null, + }), + }); + + if (response.ok) { + // Reload data + await loadJobData(); + setNotification({ message: 'Service record updated successfully!', type: 'success' }); + } else { + const error = await response.json(); + setNotification({ message: `Failed to update: ${error.error}`, type: 'error' }); + } + } catch (error) { + console.error('Error saving service record:', error); + setNotification({ message: 'Failed to save changes', type: 'error' }); + } finally { + setSaving(false); + } + }; + + const handleAddAction = async () => { + try { + setSaving(true); + const response = await fetch(`/api/admin/jobs/${jobId}/actions`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action_type: newActionType, + part_type: newPartType || null, + part_id: newPartId || null, + custom_price: newCustomPrice ? parseInt(newCustomPrice) : -1, + labour_fee: newLabourFee ? parseInt(newLabourFee) : 0, + }), + }); + + if (response.ok) { + await loadJobData(); + setShowAddAction(false); + // Reset form + setNewActionType(''); + setNewPartType(''); + setNewPartId(''); + setNewCustomPrice(''); + setNewLabourFee(''); + setNotification({ message: 'Job action added successfully!', type: 'success' }); + } else { + const error = await response.json(); + setNotification({ message: `Failed to add action: ${error.error}`, type: 'error' }); + } + } catch (error) { + console.error('Error adding action:', error); + setNotification({ message: 'Failed to add action', type: 'error' }); + } finally { + setSaving(false); + } + }; + + const handleEditAction = (action: JobAction) => { + setEditingAction(action); + setNewActionType(action.action_type); + setNewPartType(action.part_type || ''); + setNewPartId(action.part_id || ''); + setNewCustomPrice(action.custom_price > 0 && action.custom_price !== -1 ? action.custom_price.toString() : ''); + setNewLabourFee(action.labour_fee > 0 ? action.labour_fee.toString() : ''); + setShowAddAction(true); + }; + + const handleUpdateAction = async () => { + if (!editingAction) return; + + try { + setSaving(true); + const response = await fetch(`/api/admin/jobs/${jobId}/actions/${editingAction.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action_type: newActionType, + part_type: newPartType || null, + part_id: newPartId || null, + custom_price: newCustomPrice ? parseInt(newCustomPrice) : -1, + labour_fee: newLabourFee ? parseInt(newLabourFee) : 0, + }), + }); + + if (response.ok) { + await loadJobData(); + setShowAddAction(false); + setEditingAction(null); + // Reset form + setNewActionType(''); + setNewPartType(''); + setNewPartId(''); + setNewCustomPrice(''); + setNewLabourFee(''); + setNotification({ message: 'Job action updated successfully!', type: 'success' }); + } else { + const error = await response.json(); + setNotification({ message: `Failed to update action: ${error.error}`, type: 'error' }); + } + } catch (error) { + console.error('Error updating action:', error); + setNotification({ message: 'Failed to update action', type: 'error' }); + } finally { + setSaving(false); + } + }; + + const handleDeleteAction = async (actionId: number) => { + if (!confirm('Are you sure you want to delete this job action?')) return; + + try { + setSaving(true); + const response = await fetch(`/api/admin/jobs/${jobId}/actions/${actionId}`, { + method: 'DELETE', + }); + + if (response.ok) { + await loadJobData(); + setNotification({ message: 'Job action deleted successfully!', type: 'success' }); + } else { + const error = await response.json(); + setNotification({ message: `Failed to delete action: ${error.error}`, type: 'error' }); + } + } catch (error) { + console.error('Error deleting action:', error); + setNotification({ message: 'Failed to delete action', type: 'error' }); + } finally { + setSaving(false); + } + }; + + const cancelEdit = () => { + setShowAddAction(false); + setEditingAction(null); + setNewActionType(''); + setNewPartType(''); + setNewPartId(''); + setNewCustomPrice(''); + setNewLabourFee(''); + }; + + const getStatusLabel = (status: number) => { + switch (status) { + case 0: return 'Pending'; + case 1: return 'Active'; + case 2: return 'Waiting for Customer Response'; + case 3: return 'Waiting for Parts'; + case 10: return 'Completed'; + default: return 'Unknown'; + } + }; + + const getStatusColor = (status: number) => { + switch (status) { + case 0: return 'text-white/60'; + case 1: return 'text-cyan-400'; + case 2: return 'text-yellow-400'; + case 3: return 'text-orange-400'; + case 10: return 'text-green-400'; + default: return 'text-white/60'; + } + }; + + if (!isAuthenticated) { + return null; + } + + if (loading) { + return ( + <> + +
+
+
+
+

Loading...

+
+
+
+ + ); + } + + return ( + <> + +
+ {/* Animated background elements */} +
+
+
+
+ +
+
+
+
+
+
+
+

+ Edit Job #{serviceRecord?.id} +

+
+ + ← Back to Jobs + +
+ + {/* Vehicle Info */} + {serviceRecord?.Vehicles && ( +
+
+ {serviceRecord.Vehicles.id} +
+
+ {serviceRecord.Vehicles.make} {serviceRecord.Vehicles.model} + {serviceRecord.Vehicles.capacity && ` (${serviceRecord.Vehicles.capacity}cc)`} +
+
+ )} + + {/* Service Record Edit Section */} +
+

+ Service Record Details +

+ +
+
+ + +
+ +
+ + 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" + /> +
+ +
+ +