Real-Time Device Tracking
Repair Job #{job.job_no}
Service: {job.service_name} • Device: {job.device_brand}{' '}
{job.device_model}
{job.status}
Service Timeline
{job.events?.length ? (
{job.events.map((event: any, index: number) => (
{event.event_type}
{event.notes}
{new Date(event.timestamp).toLocaleString()}
))}
) : (
No timeline events yet.
)}
{/* Inspection Done & Ready for Delivery Proof Media Sections (Image 2 Location) */}
{(() => {
const mediaList: any[] = Array.isArray(job.media) ? job.media : [];
const inspectionMedia = mediaList.filter((m) => m.category === 'INSPECTION_DONE');
const deliveryMedia = mediaList.filter((m) => m.category === 'READY_FOR_DELIVERY');
if (inspectionMedia.length === 0 && deliveryMedia.length === 0) return null;
return (
{/* Inspection Done Card */}
{inspectionMedia.length > 0 && (
Inspection Done
Technician Media Proof
{inspectionMedia.map((item: any, idx: number) => {
const isVideo = item.mime_type?.startsWith('video/') || item.url?.endsWith('.mp4') || item.url?.endsWith('.webm');
return (
{isVideo ? (
) : (
setPreviewImage(item.url)}
>
)}
);
})}
)}
{/* Ready for Delivery Card */}
{deliveryMedia.length > 0 && (
Ready for Delivery
Repaired Product Proof
{deliveryMedia.map((item: any, idx: number) => {
const isVideo = item.mime_type?.startsWith('video/') || item.url?.endsWith('.mp4') || item.url?.endsWith('.webm');
return (
{isVideo ? (
) : (
setPreviewImage(item.url)}
>
)}
);
})}
)}
);
})()}
{(() => {
const payments = Array.isArray(job.payments)
? job.payments
: job.payment
? [job.payment]
: [];
const payment = payments[0] || job.payment;
const basePrice = Number(job.base_price || 0);
const doorstepFee = job.fulfillment_type === 'DOORSTEP_PICKUP' ? 250 : 0;
const totalQuote = basePrice + doorstepFee;
const paid = Number(payment?.amount || (totalQuote ? totalQuote * 0.2 : 0));
const remaining = Math.max(totalQuote - paid, 0);
const payStatusRaw =
payment?.status ||
payment?.payment_status ||
(job.status === 'BOOKING_PENDING' ? 'PENDING' : 'PAID');
const payStatusUpper = String(payStatusRaw).toUpperCase();
const isPaidStatus = ['CAPTURED', 'PAID', 'SUCCESS', 'COMPLETED'].includes(payStatusUpper);
const displayPayStatus = isPaidStatus ? 'PAID' : payStatusUpper.replace(/_/g, ' ');
return (
Payment
Status
{displayPayStatus}
{paid > 0 && (
Advance paid
{formatCurrency(paid)}
)}
{totalQuote > 0 && (
Balance due
{formatCurrency(remaining)}
)}
);
})()}
{job.appointment && (
Booking Schedule
{new Date(
job.appointment.scheduled_start
).toLocaleDateString('en-US', {
weekday: 'long',
month: 'short',
day: 'numeric',
})}
Start:{' '}
{new Date(
job.appointment.scheduled_start
).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
})}
)}
{job.quote && (
Estimate Quote V
{job.quote.version}
Subtotal
{formatCurrency(job.quote.subtotal)}
{job.quote.additional_damage_amount > 0 && (
Additional Damage
+{formatCurrency(job.quote.additional_damage_amount)}
)}
GST Tax (18%)
{formatCurrency(job.quote.tax)}
Revised Total
{formatCurrency(job.quote.total)}
{job.quote.status === 'PENDING_CUSTOMER' && (
{job.quote.expires_at && (
Expires on:{' '}
{new Date(job.quote.expires_at).toLocaleDateString()}
)}
)}
{job.status === 'QUOTE_ACCEPTED' &&
job.quote.additional_damage_amount > 0 && (
)}
)}
{job.status === 'FINAL_PAYMENT_PENDING' && (
Pay Outstanding Balance
Repair complete! Please pay the remaining balance to download
the tax invoice and schedule pickup.
)}
Track another job