ROYAL MOTORS

Management Portal

Private team access

const SUPABASE_URL = "https://fyoakqtzkuukpfnclkkp.supabase.co"; const SUPABASE_ANON_KEY = "sb_publishable_JSb_evk-KjMKGm2HRgGLpg_eoNHc-sL"; const sb = supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); let currentUser = null, currentMember = null; function $(id){return document.getElementById(id)} function escapeHtml(v){return String(v??'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]))} async function login(){ $("loginError").classList.add("hidden"); const email=$("loginEmail").value.trim(), password=$("loginPassword").value; const {data,error}=await sb.auth.signInWithPassword({email,password}); if(error){$("loginError").textContent=error.message;$("loginError").classList.remove("hidden");return} await loadSession(data.user); } async function loadSession(user){ currentUser=user; const { data, error } = await sb.rpc("get_my_team_profile").single(); if(error || !data){ await sb.auth.signOut(); $("loginError").textContent="Your login exists, but no active team profile is linked to it."; $("loginError").classList.remove("hidden"); return; } currentMember=data; $("login").classList.add("hidden"); $("app").classList.remove("hidden"); $("userRole").textContent=data.role.replaceAll("_"," "); showPage("dashboard"); } async function logout(){await sb.auth.signOut();location.reload()} function showPage(page){ ["dashboard","customers","vehicles","workorders","invoices"].forEach(p=>$("page-"+p).classList.toggle("hidden",p!==page)); if(page==="dashboard")loadDashboard(); if(page==="customers")loadCustomers(); if(page==="vehicles")loadVehicles(); if(page==="workorders")loadWorkOrders(); if(page==="invoices")loadInvoices(); } async function loadDashboard(){ const [c,v,w,i]=await Promise.all([ sb.from("customers").select("id",{count:"exact",head:true}), sb.from("vehicles").select("id",{count:"exact",head:true}), sb.from("work_orders").select("id",{count:"exact",head:true}).not("status","in","('CLOSED','CANCELLED')"), sb.from("invoices").select("total") ]); const outstanding=(i.data||[]).reduce((s,x)=>s+Number(x.total||0),0); if(c.error){ $("page-dashboard").innerHTML=`

Welcome, ${escapeHtml(currentMember.name)}

Customer count error: ${escapeHtml(c.error.message)}
`; return; } $("page-dashboard").innerHTML=`

Welcome, ${escapeHtml(currentMember.name)}

Your ${escapeHtml(currentMember.role.replaceAll("_"," ").toLowerCase())} dashboard.

Customers
${c.count ?? 0}
Vehicles
${v.count ?? 0}
Active Jobs
${w.count ?? 0}
Invoices Total
₹${outstanding.toLocaleString('en-IN')}

Quick Actions

Workflow

Customer → Vehicle → Work Order → Installer → Manager Approval → Accounts → Invoice → Payment

`; } async function loadCustomers(){ const {data,error}=await sb .from("customers") .select("*") .order("created_at",{ascending:false}); $("page-customers").innerHTML=`

Customers

Customer database

${(data||[]).map(x=>` `).join("")}
Customer Phone WhatsApp City Type
${escapeHtml(x.phone)} ${escapeHtml(x.whatsapp)} ${escapeHtml(x.city)} ${escapeHtml(x.customer_type)}
${error ? `
${escapeHtml(error.message)}
` : ""} `; // Attach customer buttons after the table is created document.querySelectorAll(".customer-btn").forEach(button=>{ button.addEventListener("click",function(){ viewCustomer(this.dataset.customerId); }); }); } function openCustomer(){$("customerModal").classList.remove("hidden")} function closeCustomer(){$("customerModal").classList.add("hidden");$("customerError").classList.add("hidden")} let selectedCustomerId = null; async function viewCustomer(customerId){ selectedCustomerId = customerId; const {data: customer, error: customerError} = await sb .from("customers") .select("*") .eq("id", customerId) .single(); if(customerError){ $("page-customers").innerHTML = `
${escapeHtml(customerError.message)}
`; return; } const {data: vehicles, error: vehicleError} = await sb .from("vehicles") .select("*") .eq("customer_id", customerId) .order("created_at",{ascending:false}); $("page-customers").innerHTML = `

${escapeHtml(customer.name)}

${escapeHtml(customer.phone || "")} ${customer.city ? " • " + escapeHtml(customer.city) : ""}

Vehicles

${ vehicleError ? `
${escapeHtml(vehicleError.message)}
` : vehicles && vehicles.length ? `
${vehicles.map(v => `

${escapeHtml( ((v.make || "") + " " + (v.model || "")).trim() )}

${escapeHtml(v.registration_number)}
VEHICLE
Colour ${escapeHtml(v.colour || "—")}
Year ${escapeHtml(v.manufacturing_year || "—")}
VIN / Chassis ${escapeHtml(v.vin || "—")}
`).join("")}
` : `

No vehicles yet

Add the first vehicle for this customer.

` }
`; } function openVehicle(customerId, customerName){ selectedCustomerId = customerId; $("vehicleCustomerName").textContent = "Customer: " + customerName; $("vMake").value = ""; $("vModel").value = ""; $("vRegistration").value = ""; $("vColour").value = ""; $("vYear").value = ""; $("vVin").value = ""; $("vehicleError").classList.add("hidden"); $("vehicleModal").classList.remove("hidden"); } function closeVehicle(){ $("vehicleModal").classList.add("hidden"); $("vehicleError").classList.add("hidden"); } async function saveVehicle(){ $("vehicleError").classList.add("hidden"); const payload = { customer_id: selectedCustomerId, make: $("vMake").value.trim(), model: $("vModel").value.trim(), registration_number: $("vRegistration").value.trim(), colour: $("vColour").value.trim(), manufacturing_year: $("vYear").value ? Number($("vYear").value) : null, vin: $("vVin").value.trim() || null }; if(!payload.customer_id){ $("vehicleError").textContent = "Please select a customer."; $("vehicleError").classList.remove("hidden"); return; } if(!payload.model || !payload.registration_number){ $("vehicleError").textContent = "Model/Variant and Registration Number are required."; $("vehicleError").classList.remove("hidden"); return; } const {error} = await sb .from("vehicles") .insert(payload); if(error){ $("vehicleError").textContent = error.message; $("vehicleError").classList.remove("hidden"); return; } closeVehicle(); await loadVehicles(); await loadDashboard(); await viewCustomer(selectedCustomerId); } async function saveCustomer(){ $("customerError").classList.add("hidden"); const payload={ name:$("cName").value.trim(), phone:$("cPhone").value.trim(), whatsapp:$("cWhatsapp").value.trim(), email:$("cEmail").value.trim(), address:$("cAddress").value.trim(), city:$("cCity").value.trim(), state:$("cState").value.trim(), pincode:$("cPincode").value.trim(), customer_type:$("cType").value, assigned_sales_person: currentMember.role==="SALES_PERSON" ? currentMember.id : null }; if(!payload.name || !payload.phone){ $("customerError").textContent="Name and phone are required."; $("customerError").classList.remove("hidden"); return; } const {error}=await sb.from("customers").insert(payload); if(error){ $("customerError").textContent=error.message; $("customerError").classList.remove("hidden"); return; } closeCustomer(); // Refresh both customer list and dashboard statistics await loadCustomers(); await loadDashboard(); } async function loadVehicles(){ const {data,error}=await sb.from("vehicles").select("*,customers(name)").order("created_at",{ascending:false}); $("page-vehicles").innerHTML=`

Vehicles

Vehicle profiles and service history.

${(data||[]).map(x=>``).join("")}
RegistrationVehicleCustomerColourYear
${escapeHtml(x.registration_number)}${escapeHtml((x.make||"")+" "+x.model)}${escapeHtml(x.customers?.name)}${escapeHtml(x.colour)}${escapeHtml(x.manufacturing_year)}
${error?`
${escapeHtml(error.message)}
`:""}`; } async function loadWorkOrders(){ const {data,error}=await sb .from("work_orders") .select("*,customers(name),vehicles(model,registration_number)") .order("created_at",{ascending:false}); $("page-workorders").innerHTML=`

Work Orders

Current and historical jobs.

${(data||[]).map(x=>` `).join("")}
Work Order Customer Vehicle Status Priority
${escapeHtml(x.customers?.name)} ${escapeHtml( (x.vehicles?.model||"") + " " + (x.vehicles?.registration_number||"") )} ${escapeHtml(x.status)} ${escapeHtml(x.priority)}
${error ? `
${escapeHtml(error.message)}
` : "" } `; } function openWorkOrder(){ $("workOrderError").classList.add("hidden"); $("woCustomer").innerHTML=` `; $("woVehicle").innerHTML=` `; $("woPriority").value="NORMAL"; $("woStartDate").value=""; $("woCompletionDate").value=""; $("woCustomerInstructions").value=""; $("woInternalNotes").value=""; $("workOrderModal").classList.remove("hidden"); loadWorkOrderCustomers(); } function closeWorkOrder(){ $("workOrderModal").classList.add("hidden"); $("workOrderError").classList.add("hidden"); } async function loadWorkOrderCustomers(){ const {data,error}=await sb .from("customers") .select("id,name,phone") .order("name",{ascending:true}); if(error){ $("woCustomer").innerHTML=` `; $("workOrderError").textContent=error.message; $("workOrderError").classList.remove("hidden"); return; } $("woCustomer").innerHTML=` ${(data||[]).map(c=>` `).join("")} `; } async function loadWorkOrderVehicles(){ const customerId=$("woCustomer").value; $("woVehicle").innerHTML=` `; if(!customerId){ $("woVehicle").innerHTML=` `; return; } const {data,error}=await sb .from("vehicles") .select("id,make,model,registration_number,colour") .eq("customer_id",customerId) .order("created_at",{ascending:false}); if(error){ $("woVehicle").innerHTML=` `; $("workOrderError").textContent=error.message; $("workOrderError").classList.remove("hidden"); return; } if(!data || !data.length){ $("woVehicle").innerHTML=` `; return; } $("woVehicle").innerHTML=` ${data.map(v=>` `).join("")} `; } function generateWorkOrderNumber(){ const now=new Date(); const year=now.getFullYear(); const month=String(now.getMonth()+1).padStart(2,"0"); const day=String(now.getDate()).padStart(2,"0"); const time= String(now.getHours()).padStart(2,"0")+ String(now.getMinutes()).padStart(2,"0")+ String(now.getSeconds()).padStart(2,"0"); return `RM-WO-${year}${month}${day}-${time}`; } async function saveWorkOrder(){ $("workOrderError").classList.add("hidden"); const customerId=$("woCustomer").value; const vehicleId=$("woVehicle").value; if(!customerId){ $("workOrderError").textContent= "Please select a customer."; $("workOrderError").classList.remove("hidden"); return; } if(!vehicleId){ $("workOrderError").textContent= "Please select a vehicle."; $("workOrderError").classList.remove("hidden"); return; } const payload={ work_order_number:generateWorkOrderNumber(), customer_id:customerId, vehicle_id:vehicleId, sales_person_id: currentMember.role==="SALES_PERSON" ? currentMember.id : null, priority:$("woPriority").value, status:"DRAFT", expected_start_date: $("woStartDate").value || null, expected_completion_date: $("woCompletionDate").value || null, customer_instructions: $("woCustomerInstructions").value.trim() || null, internal_notes: $("woInternalNotes").value.trim() || null }; const {error}=await sb .from("work_orders") .insert(payload); if(error){ $("workOrderError").textContent=error.message; $("workOrderError").classList.remove("hidden"); return; } closeWorkOrder(); await loadWorkOrders(); await loadDashboard(); } async function loadInvoices(){ const {data,error}=await sb.from("invoices").select("*,customers(name),vehicles(model,registration_number)").order("created_at",{ascending:false}); $("page-invoices").innerHTML=`

Invoices

Billing and payment records.

${(data||[]).map(x=>``).join("")}
InvoiceCustomerVehicleTotalStatus
${escapeHtml(x.invoice_number)}${escapeHtml(x.customers?.name)}${escapeHtml((x.vehicles?.model||"")+" "+(x.vehicles?.registration_number||""))}₹${Number(x.total||0).toLocaleString('en-IN')}${escapeHtml(x.payment_status)}
${error?`
${escapeHtml(error.message)}
`:""}`; } sb.auth.getSession().then(({data})=>{if(data.session)loadSession(data.session.user)}); sb.auth.onAuthStateChange((_event,session)=>{if(session&&!currentMember)loadSession(session.user)}); async function viewWorkOrder(workOrderId){ const {data: workOrder, error} = await sb .from("work_orders") .select(` *, customers(name, phone, whatsapp), vehicles(make, model, registration_number, colour) `) .eq("id", workOrderId) .single(); const {data: workOrderItems, error: itemsError} = await sb .from("work_order_items") .select("*") .eq("work_order_id", workOrderId) .order("created_at", {ascending:true}); const totalEstimated = (workOrderItems || []).reduce( (sum, item) => sum + (Number(item.estimated_price || 0) * Number(item.quantity || 1)), 0 ); const totalFinal = (workOrderItems || []).reduce( (sum, item) => sum + (Number(item.final_price || 0) * Number(item.quantity || 1)), 0 ); if(error){ $("page-workorders").innerHTML = `
${escapeHtml(error.message)}
`; return; } $("page-workorders").innerHTML = `

${escapeHtml(workOrder.work_order_number)}

Work Order Details

Job Information

Customer ${escapeHtml(workOrder.customers?.name || "—")}
Phone ${escapeHtml(workOrder.customers?.phone || "—")}
Vehicle ${escapeHtml( ((workOrder.vehicles?.make || "") + " " + (workOrder.vehicles?.model || "")).trim() || "—" )}
Registration ${escapeHtml( workOrder.vehicles?.registration_number || "—" )}
Colour ${escapeHtml(workOrder.vehicles?.colour || "—")}
Priority ${escapeHtml(workOrder.priority || "NORMAL")}
Status ${escapeHtml(workOrder.status || "DRAFT")}

Expected Dates

Expected Start ${escapeHtml(workOrder.expected_start_date || "—")}
Expected Completion ${escapeHtml(workOrder.expected_completion_date || "—")}

Customer Instructions

${escapeHtml( workOrder.customer_instructions || "No customer instructions." )}

Internal Notes

${escapeHtml( workOrder.internal_notes || "No internal notes." )}

Fittings & Services

${ workOrderItems && workOrderItems.length ? `
${workOrderItems.map(item => ` `).join("")}
Service / Fitting Qty Estimated Final Status Actions
${escapeHtml(item.service_name)} ${ item.description ? `
${escapeHtml(item.description)}
` : "" }
${escapeHtml(item.quantity)} ₹${Number(item.estimated_price || 0).toLocaleString('en-IN')} ₹${Number(item.final_price || 0).toLocaleString('en-IN')} ${escapeHtml(item.status || "PENDING")}
` : `

No fittings or services have been added yet.

` }
Total Estimated ₹${totalEstimated.toLocaleString('en-IN')}
Total Final ₹${totalFinal.toLocaleString('en-IN')}
`; } let selectedWorkOrderId = null; let editingServiceItemId = null; function openServiceItem(workOrderId){ selectedWorkOrderId = workOrderId; editingServiceItemId = null; $("serviceItemSaveText").textContent = "Save Service"; $("siName").value = ""; $("siDescription").value = ""; $("siQuantity").value = "1"; $("siEstimatedPrice").value = "0"; $("siFinalPrice").value = "0"; $("siManagerNotes").value = ""; $("siInstallerNotes").value = ""; $("serviceItemError").classList.add("hidden"); $("serviceItemModal").classList.remove("hidden"); } function closeServiceItem(){ $("serviceItemModal").classList.add("hidden"); $("serviceItemError").classList.add("hidden"); editingServiceItemId = null; $("serviceItemModal").querySelector("h2").textContent = "Add Fitting / Service"; } async function saveServiceItem(){ $("serviceItemError").classList.add("hidden"); if(!selectedWorkOrderId){ $("serviceItemError").textContent = "Work Order not selected."; $("serviceItemError").classList.remove("hidden"); return; } const payload = { work_order_id: selectedWorkOrderId, service_name: $("siName").value.trim(), description: $("siDescription").value.trim() || null, quantity: Number($("siQuantity").value || 1), estimated_price: Number($("siEstimatedPrice").value || 0), final_price: Number($("siFinalPrice").value || 0), status: "PENDING", manager_notes: $("siManagerNotes").value.trim() || null, installer_notes: $("siInstallerNotes").value.trim() || null }; if(!payload.service_name){ $("serviceItemError").textContent = "Please enter the Service / Fitting Name."; $("serviceItemError").classList.remove("hidden"); return; } if(payload.quantity <= 0){ $("serviceItemError").textContent = "Quantity must be at least 1."; $("serviceItemError").classList.remove("hidden"); return; } let error; if(editingServiceItemId){ const result = await sb .from("work_order_items") .update(payload) .eq("id", editingServiceItemId); error = result.error; }else{ const result = await sb .from("work_order_items") .insert(payload); error = result.error; } if(error){ $("serviceItemError").textContent = error.message; $("serviceItemError").classList.remove("hidden"); return; } closeServiceItem(); await viewWorkOrder(selectedWorkOrderId); } async function editServiceItem(itemId, workOrderId){ const {data:item, error} = await sb .from("work_order_items") .select("*") .eq("id", itemId) .single(); if(error){ alert(error.message); return; } selectedWorkOrderId = workOrderId; editingServiceItemId = itemId; $("serviceItemSaveText").textContent = "Update Service"; $("siName").value = item.service_name || ""; $("siDescription").value = item.description || ""; $("siQuantity").value = item.quantity || 1; $("siEstimatedPrice").value = item.estimated_price || 0; $("siFinalPrice").value = item.final_price || 0; $("siManagerNotes").value = item.manager_notes || ""; $("siInstallerNotes").value = item.installer_notes || ""; $("serviceItemError").classList.add("hidden"); $("serviceItemModal").querySelector("h2").textContent = "Edit Fitting / Service"; $("serviceItemModal").classList.remove("hidden"); } async function deleteServiceItem(itemId, workOrderId){ const confirmed = confirm( "Are you sure you want to delete this fitting / service?" ); if(!confirmed) return; const {error} = await sb .from("work_order_items") .delete() .eq("id", itemId); if(error){ alert(error.message); return; } await viewWorkOrder(workOrderId); } async function loadInstallers(){ const {data,error} = await sb .from("team_members") .select("id,name,phone") .eq("role","INSTALLER") .eq("is_active",true) .order("name",{ascending:true}); if(error){ $("assignInstallerSelect").innerHTML = ``; $("assignInstallerError").textContent = error.message; $("assignInstallerError").classList.remove("hidden"); return; } $("assignInstallerSelect").innerHTML = ` ${(data || []).map(installer => ` `).join("")} `; } async function openAssignInstaller(workOrderId){ selectedWorkOrderId = workOrderId; $("assignInstallerError").classList.add("hidden"); $("assignInstallerWorkOrder").textContent = "Assign an installer to Work Order: " + workOrderId; $("assignInstallerSelect").innerHTML = ``; $("assignScheduledStart").value = ""; $("assignScheduledEnd").value = ""; $("assignInstallerNotes").value = ""; $("assignInstallerModal").classList.remove("hidden"); await loadInstallers(); } function closeAssignInstaller(){ $("assignInstallerModal").classList.add("hidden"); $("assignInstallerError").classList.add("hidden"); $("assignInstallerSelect").value = ""; $("assignScheduledStart").value = ""; $("assignScheduledEnd").value = ""; $("assignInstallerNotes").value = ""; } async function saveInstallerAssignment(){ $("assignInstallerError").classList.add("hidden"); if(!selectedWorkOrderId){ $("assignInstallerError").textContent = "Work Order not selected."; $("assignInstallerError").classList.remove("hidden"); return; } const installerId = $("assignInstallerSelect").value; if(!installerId){ $("assignInstallerError").textContent = "Please select an installer."; $("assignInstallerError").classList.remove("hidden"); return; } const scheduledStart = $("assignScheduledStart").value ? new Date($("assignScheduledStart").value).toISOString() : null; const scheduledEnd = $("assignScheduledEnd").value ? new Date($("assignScheduledEnd").value).toISOString() : null; if(scheduledStart && scheduledEnd){ if(new Date(scheduledEnd) <= new Date(scheduledStart)){ $("assignInstallerError").textContent = "Scheduled End must be after Scheduled Start."; $("assignInstallerError").classList.remove("hidden"); return; } } const payload = { work_order_id: selectedWorkOrderId, installer_id: installerId, assigned_by: currentMember.id, scheduled_start: scheduledStart, scheduled_end: scheduledEnd, status: "ASSIGNED", installer_notes: $("assignInstallerNotes").value.trim() || null }; const {error} = await sb .from("installer_assignments") .insert(payload); if(error){ $("assignInstallerError").textContent = error.message; $("assignInstallerError").classList.remove("hidden"); return; } closeAssignInstaller(); await viewWorkOrder(selectedWorkOrderId); }