ROYAL MOTORS Management Portal
Add Vehicle
New Customer
New Work Order
Create a new job for a customer vehicle.
Add Fitting / Service
Add a service or fitting to this Work Order.
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}
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=`
| Customer |
Phone |
WhatsApp |
City |
Type |
${(data||[]).map(x=>`
|
|
${escapeHtml(x.phone)} |
${escapeHtml(x.whatsapp)} |
${escapeHtml(x.city)} |
${escapeHtml(x.customer_type)} |
`).join("")}
${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 = `
Vehicles
${
vehicleError
? `
${escapeHtml(vehicleError.message)}
`
: vehicles && vehicles.length
? `
${vehicles.map(v => `
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.
| Registration | Vehicle | Customer | Colour | Year |
${(data||[]).map(x=>`| ${escapeHtml(x.registration_number)} | ${escapeHtml((x.make||"")+" "+x.model)} | ${escapeHtml(x.customers?.name)} | ${escapeHtml(x.colour)} | ${escapeHtml(x.manufacturing_year)} |
`).join("")}
${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 Order |
Customer |
Vehicle |
Status |
Priority |
${(data||[]).map(x=>`
|
|
${escapeHtml(x.customers?.name)}
|
${escapeHtml(
(x.vehicles?.model||"") +
" " +
(x.vehicles?.registration_number||"")
)}
|
${escapeHtml(x.status)}
|
${escapeHtml(x.priority)}
|
`).join("")}
${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.
| Invoice | Customer | Vehicle | Total | Status |
${(data||[]).map(x=>`| ${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)} |
`).join("")}
${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 = `
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
? `
| Service / Fitting |
Qty |
Estimated |
Final |
Status |
Actions |
${workOrderItems.map(item => `
|
${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")}
|
|
`).join("")}
`
: `
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);
}