medusajs-documents-plugin 1.1.61 → 1.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { defineRouteConfig } from "@medusajs/admin-sdk";
3
3
  import { Container, Heading, Text, Button, Input, Select, Tooltip, IconButton, Switch, Label, Table, Badge, DropdownMenu, Textarea, FocusModal } from "@medusajs/ui";
4
4
  import { useState, useRef, useEffect, useMemo, useCallback, Fragment } from "react";
5
- import { DocumentText, ArrowPath, Plus, Sparkles, ChevronDown, ChevronRight, ArrowDownTray, PencilSquare, Envelope, Trash } from "@medusajs/icons";
5
+ import { DocumentText, ArrowPath, Plus, Sparkles, ChevronDown, ChevronRight, ArrowDownTray, PencilSquare, Envelope, Trash, MagnifyingGlass, XMark } from "@medusajs/icons";
6
6
  function extractBaseReference(displayId) {
7
7
  if (!displayId) return "";
8
8
  const cleaned = String(displayId).trim();
@@ -329,31 +329,6 @@ The recipient confirms that the device has been inspected, tested in their prese
329
329
  },
330
330
  notes: "By accepting handover, the client acknowledges satisfactory return of all custody items and confirms device operability."
331
331
  },
332
- {
333
- id: "proforma",
334
- name: "Commercial Quotation / Proforma",
335
- icon: "💼",
336
- badge: "Cost Estimate",
337
- title: "Commercial Quotation (Proforma)",
338
- subject: "Commercial Quotation & Scope of Work - Urban Device Care",
339
- body_text: `Thank you for choosing Urban Device Care. Below is an itemized commercial quotation detailing required OEM components, workshop labor, precision calibration, and expected turnaround schedule for your equipment overhaul.
340
-
341
- This quotation is valid for 14 calendar days from the date of issue. Upon formal approval, component procurement and bench allocation will commence immediately.`,
342
- items: [
343
- { title: "OEM Liquid Retina XDR Display Assembly", description: "Brand new original factory panel with factory color calibration", quantity: 1, unit_price: 42e3 },
344
- { title: "Clean-Room Workshop Installation & Sensor Alignment", description: "Certified clean-room installation, gasket replacement & TrueTone calibration", quantity: 1, unit_price: 6500 },
345
- { title: "Hydrophobic Dust & Water Ingress Seal Restoration", description: "OEM adhesive barrier pressure cured to IP68 rating", quantity: 1, unit_price: 2500 }
346
- ],
347
- custom_fields: {
348
- "Device Model": "Apple iPad Pro 12.9-inch (6th Gen - Wi-Fi + Cellular)",
349
- "Serial / IMEI": "DLXW9201M93K",
350
- "Ticket Reference": "RT-2024-4190",
351
- "Estimator / Technician": "Bonifencia A.",
352
- "Validity Period": "14 Calendar Days",
353
- "Estimated Turnaround": "24 - 48 Hours"
354
- },
355
- notes: "All replaced hardware carries our standard 90-day warranty coverage. Payment accepted via M-Pesa, Bank Transfer, or Credit/Debit Card."
356
- },
357
332
  {
358
333
  id: "trade_in",
359
334
  name: "Device Trade-In Agreement",
@@ -377,27 +352,358 @@ The seller warrants that they are the sole lawful owner of the equipment, that i
377
352
  "Evaluation Specialist": "Bonifencia A."
378
353
  },
379
354
  notes: "Ownership transfers immediately upon document execution. Valuation is binding and irrevocable once trade-in payout or store credit is issued."
380
- },
381
- {
382
- id: "blank",
383
- name: "Blank Custom Document",
384
- icon: "📝",
385
- badge: "Custom",
386
- title: "Official Document",
387
- subject: "Official Document from Urban Device Care",
388
- body_text: "This official technical document confirms the specifications, service terms, and verified conditions documented by Urban Device Care for the specified customer asset.",
389
- items: [
390
- { title: "Professional Hardware Inspection & Maintenance Service", description: "Certified technical evaluation and service clearance", quantity: 1, unit_price: 0 }
391
- ],
392
- custom_fields: {
393
- "Device Model": "Computing Asset / Hardware Module",
394
- "Serial / IMEI": "SN-XXXX-XXXX",
395
- "Ticket Reference": "RT-2024-0001",
396
- "Technician": "Bonifencia A."
397
- },
398
- notes: "Standard terms and conditions apply. For inquiries, contact info@urbandevicecare.co.uk."
399
355
  }
400
356
  ];
357
+ const CustomerAutofillCombobox = ({
358
+ candidates,
359
+ loading = false,
360
+ onSelectCustomer,
361
+ label = "Quick Autofill Customer Details"
362
+ }) => {
363
+ const [query, setQuery] = useState("");
364
+ const [isOpen, setIsOpen] = useState(false);
365
+ const [autofilledNotice, setAutofilledNotice] = useState(null);
366
+ const [remoteCandidates, setRemoteCandidates] = useState([]);
367
+ const [isSearchingRemote, setIsSearchingRemote] = useState(false);
368
+ const containerRef = useRef(null);
369
+ useEffect(() => {
370
+ const handleClickOutside = (e) => {
371
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
372
+ setIsOpen(false);
373
+ }
374
+ };
375
+ document.addEventListener("mousedown", handleClickOutside);
376
+ return () => document.removeEventListener("mousedown", handleClickOutside);
377
+ }, []);
378
+ useEffect(() => {
379
+ const q = query.trim();
380
+ if (q.length < 2) {
381
+ setRemoteCandidates([]);
382
+ setIsSearchingRemote(false);
383
+ return;
384
+ }
385
+ const timer = setTimeout(async () => {
386
+ setIsSearchingRemote(true);
387
+ try {
388
+ const res = await fetch(`/admin/customers?q=${encodeURIComponent(q)}&fields=*addresses&limit=25`, {
389
+ credentials: "include"
390
+ });
391
+ if (res.ok) {
392
+ const data = await res.json().catch(() => ({}));
393
+ if (data.customers && Array.isArray(data.customers)) {
394
+ const mapped = data.customers.map((c) => {
395
+ var _a, _b, _c;
396
+ const fullName = [c.first_name, c.last_name].filter(Boolean).join(" ").trim();
397
+ const addr = ((_a = c.addresses) == null ? void 0 : _a[0]) || c.default_billing_address || c.billing_address;
398
+ const formattedAddr = addr ? [addr.address_1, addr.address_2, addr.city, addr.postal_code, addr.province, (_b = addr.country_code) == null ? void 0 : _b.toUpperCase()].filter(Boolean).join(", ") : "";
399
+ const company = c.company_name || (addr == null ? void 0 : addr.company) || ((_c = c.metadata) == null ? void 0 : _c.company_name) || "";
400
+ const name = fullName || company || c.email || "Customer";
401
+ return {
402
+ id: `remote-${c.id}`,
403
+ name,
404
+ company_name: company,
405
+ email: c.email || "",
406
+ phone: c.phone || (addr == null ? void 0 : addr.phone) || "",
407
+ address: formattedAddr,
408
+ source: "customer",
409
+ source_label: "Medusa Customer (Live)"
410
+ };
411
+ });
412
+ setRemoteCandidates(mapped);
413
+ }
414
+ }
415
+ } catch (e) {
416
+ } finally {
417
+ setIsSearchingRemote(false);
418
+ }
419
+ }, 250);
420
+ return () => clearTimeout(timer);
421
+ }, [query]);
422
+ const allCandidates = useMemo(() => {
423
+ var _a, _b, _c, _d;
424
+ if (remoteCandidates.length === 0) return candidates;
425
+ const map = /* @__PURE__ */ new Map();
426
+ for (const c of candidates) {
427
+ const key = ((_a = c.email) == null ? void 0 : _a.toLowerCase()) || ((_b = c.phone) == null ? void 0 : _b.replace(/\s+/g, "")) || `${c.name.toLowerCase()}::${c.company_name.toLowerCase()}`;
428
+ if (key) map.set(key, c);
429
+ }
430
+ for (const c of remoteCandidates) {
431
+ const key = ((_c = c.email) == null ? void 0 : _c.toLowerCase()) || ((_d = c.phone) == null ? void 0 : _d.replace(/\s+/g, "")) || `${c.name.toLowerCase()}::${c.company_name.toLowerCase()}`;
432
+ if (key) {
433
+ if (!map.has(key)) {
434
+ map.set(key, c);
435
+ } else {
436
+ const existing = map.get(key);
437
+ if (!existing.address && c.address) existing.address = c.address;
438
+ if (!existing.company_name && c.company_name) existing.company_name = c.company_name;
439
+ }
440
+ }
441
+ }
442
+ return Array.from(map.values());
443
+ }, [candidates, remoteCandidates]);
444
+ const filteredCandidates = useMemo(() => {
445
+ const q = query.trim().toLowerCase();
446
+ if (!q) {
447
+ return allCandidates.slice(0, 10);
448
+ }
449
+ const cleanQ = q.replace(/\s+/g, "");
450
+ const digitsOnlyQ = cleanQ.replace(/\D/g, "");
451
+ return allCandidates.filter((c) => {
452
+ const name = c.name.toLowerCase();
453
+ const company = c.company_name.toLowerCase();
454
+ const email = c.email.toLowerCase();
455
+ const phone = c.phone.replace(/\s+/g, "").toLowerCase();
456
+ const digitsOnlyPhone = phone.replace(/\D/g, "");
457
+ const addr = c.address.toLowerCase();
458
+ const src = c.source_label.toLowerCase();
459
+ const phoneMatches = phone.includes(cleanQ) || digitsOnlyQ.length >= 4 && (digitsOnlyPhone.includes(digitsOnlyQ) || digitsOnlyQ.includes(digitsOnlyPhone));
460
+ return name.includes(q) || company.includes(q) || email.includes(q) || phoneMatches || addr.includes(q) || src.includes(q);
461
+ }).slice(0, 20);
462
+ }, [allCandidates, query]);
463
+ const handleSelect = (candidate) => {
464
+ onSelectCustomer(candidate);
465
+ const displayName = candidate.company_name ? `${candidate.name} (${candidate.company_name})` : candidate.name;
466
+ setAutofilledNotice(`✓ Autofilled: ${displayName}`);
467
+ setQuery("");
468
+ setIsOpen(false);
469
+ setTimeout(() => {
470
+ setAutofilledNotice(null);
471
+ }, 4e3);
472
+ };
473
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative flex flex-col gap-1.5 p-3 rounded-lg bg-ui-bg-subtle border border-ui-border-base", children: [
474
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
475
+ /* @__PURE__ */ jsxs(Label, { className: "text-xs font-semibold flex items-center gap-1.5 text-ui-fg-base", children: [
476
+ /* @__PURE__ */ jsx(Sparkles, { className: "w-3.5 h-3.5 text-ui-fg-interactive" }),
477
+ label
478
+ ] }),
479
+ autofilledNotice && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-emerald-600 bg-emerald-50 dark:bg-emerald-950/40 px-2 py-0.5 rounded border border-emerald-200", children: autofilledNotice })
480
+ ] }),
481
+ /* @__PURE__ */ jsx(Text, { className: "text-[11px] text-ui-fg-subtle", children: "Search existing customer records, repair tickets, or recent documents in Medusa to automatically populate customer fields." }),
482
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
483
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none text-ui-fg-muted", children: /* @__PURE__ */ jsx(MagnifyingGlass, { className: "w-4 h-4" }) }),
484
+ /* @__PURE__ */ jsx(
485
+ "input",
486
+ {
487
+ type: "text",
488
+ value: query,
489
+ onChange: (e) => {
490
+ setQuery(e.target.value);
491
+ setIsOpen(true);
492
+ },
493
+ onFocus: (e) => {
494
+ setIsOpen(true);
495
+ e.target.select();
496
+ },
497
+ placeholder: "Type name, company, email, phone, or ticket # to search & autofill...",
498
+ className: "w-full pl-8.5 pr-8 py-1.5 text-xs bg-ui-bg-base border border-ui-border-base rounded-md text-ui-fg-base placeholder:text-ui-fg-muted focus:outline-none focus:ring-1 focus:ring-ui-border-interactive focus:border-ui-border-interactive"
499
+ }
500
+ ),
501
+ query && /* @__PURE__ */ jsx(
502
+ "button",
503
+ {
504
+ type: "button",
505
+ onClick: () => setQuery(""),
506
+ className: "absolute inset-y-0 right-0 pr-2.5 flex items-center text-ui-fg-muted hover:text-ui-fg-base",
507
+ children: /* @__PURE__ */ jsx(XMark, { className: "w-3.5 h-3.5" })
508
+ }
509
+ )
510
+ ] }),
511
+ isOpen && /* @__PURE__ */ jsx("div", { className: "absolute top-full left-0 right-0 mt-1 z-50 bg-ui-bg-base border border-ui-border-base rounded-md shadow-lg max-h-64 overflow-y-auto divide-y divide-ui-border-base", children: loading || isSearchingRemote ? /* @__PURE__ */ jsxs("div", { className: "p-3 text-xs text-ui-fg-muted text-center flex items-center justify-center gap-2", children: [
512
+ /* @__PURE__ */ jsx(ArrowPath, { className: "w-3 h-3 animate-spin" }),
513
+ "Searching customer records..."
514
+ ] }) : filteredCandidates.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-3 text-xs text-ui-fg-muted text-center", children: "No matching customer records found" }) : filteredCandidates.map((c) => {
515
+ const badgeColor = c.source === "customer" ? "blue" : c.source === "repair" ? "orange" : "green";
516
+ return /* @__PURE__ */ jsxs(
517
+ "div",
518
+ {
519
+ onClick: () => handleSelect(c),
520
+ className: "p-2.5 hover:bg-ui-bg-subtle-hover cursor-pointer transition-colors",
521
+ children: [
522
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 mb-1", children: [
523
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate", children: [
524
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-xs text-ui-fg-base truncate", children: c.name }),
525
+ c.company_name && c.company_name !== c.name && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-ui-fg-muted truncate", children: [
526
+ "(",
527
+ c.company_name,
528
+ ")"
529
+ ] })
530
+ ] }),
531
+ /* @__PURE__ */ jsx(Badge, { size: "small", color: badgeColor, className: "shrink-0 text-[10px]", children: c.source_label })
532
+ ] }),
533
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-ui-fg-subtle truncate", children: [
534
+ c.email && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
535
+ "📧 ",
536
+ c.email
537
+ ] }),
538
+ c.phone && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
539
+ "📞 ",
540
+ c.phone
541
+ ] }),
542
+ c.address && /* @__PURE__ */ jsxs("span", { className: "truncate max-w-[200px]", children: [
543
+ "📍 ",
544
+ c.address
545
+ ] })
546
+ ] })
547
+ ]
548
+ },
549
+ c.id
550
+ );
551
+ }) })
552
+ ] });
553
+ };
554
+ const RepairTicketCombobox = ({
555
+ tickets,
556
+ selectedTicketId,
557
+ onSelectTicket,
558
+ onClear,
559
+ loading = false
560
+ }) => {
561
+ const [query, setQuery] = useState("");
562
+ const [isOpen, setIsOpen] = useState(false);
563
+ const containerRef = useRef(null);
564
+ const getTicketLabel = (t) => {
565
+ var _a, _b, _c, _d, _e;
566
+ const cleanNum = String(t.ticket_number || t.id).replace(/^(RT-|JOB-|CERT-)+/i, "");
567
+ const displayRef = `RT-${cleanNum}`;
568
+ const custName = ((_a = t.customer) == null ? void 0 : _a.company_name) || t.company_name || t.customer_name || (((_b = t.customer) == null ? void 0 : _b.first_name) ? `${t.customer.first_name} ${t.customer.last_name || ""}`.trim() : "") || "Customer";
569
+ const brandModel = [(_c = t.device) == null ? void 0 : _c.brand, (_d = t.device) == null ? void 0 : _d.model_name].filter(Boolean).join(" ").trim() || ((_e = t.custom_fields) == null ? void 0 : _e["Device Model"]) || "Device";
570
+ return `${displayRef} — ${custName} (${brandModel})`;
571
+ };
572
+ useEffect(() => {
573
+ if (!selectedTicketId) {
574
+ setQuery("");
575
+ return;
576
+ }
577
+ const t = tickets.find((item) => item.id === selectedTicketId || item.ticket_number === selectedTicketId);
578
+ if (t) {
579
+ setQuery(getTicketLabel(t));
580
+ }
581
+ }, [selectedTicketId, tickets]);
582
+ useEffect(() => {
583
+ const handleClickOutside = (e) => {
584
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
585
+ setIsOpen(false);
586
+ }
587
+ };
588
+ document.addEventListener("mousedown", handleClickOutside);
589
+ return () => document.removeEventListener("mousedown", handleClickOutside);
590
+ }, []);
591
+ const filteredTickets = useMemo(() => {
592
+ const q = query.trim().toLowerCase();
593
+ const cleanQ = q.replace(/^(?:(?:rt|job|cert)[-–—\s#]*|[#])/i, "").trim().toLowerCase();
594
+ const digitsOnlyQ = cleanQ.replace(/\D/g, "");
595
+ const selectedTicket = tickets.find((item) => item.id === selectedTicketId || item.ticket_number === selectedTicketId);
596
+ const selectedLabel = selectedTicket ? getTicketLabel(selectedTicket).toLowerCase() : "";
597
+ const isUnmodifiedSelection = Boolean(selectedTicket && q === selectedLabel);
598
+ if (!q || isUnmodifiedSelection) {
599
+ return tickets.slice(0, 30);
600
+ }
601
+ return tickets.filter((t) => {
602
+ var _a, _b, _c, _d, _e, _f, _g, _h;
603
+ const cleanNum = String(t.ticket_number || t.id).replace(/^(RT-|JOB-|CERT-)+/i, "").toLowerCase();
604
+ const rawNum = String(t.ticket_number || "").toLowerCase();
605
+ const rawId = String(t.id || "").toLowerCase();
606
+ const displayRef = `rt-${cleanNum}`;
607
+ const custName = (((_a = t.customer) == null ? void 0 : _a.name) || t.customer_name || (((_b = t.customer) == null ? void 0 : _b.first_name) ? `${t.customer.first_name} ${t.customer.last_name || ""}`.trim() : "")).toLowerCase();
608
+ const compName = (((_c = t.customer) == null ? void 0 : _c.company_name) || t.company_name || "").toLowerCase();
609
+ const email = (((_d = t.customer) == null ? void 0 : _d.email) || t.customer_email || "").toLowerCase();
610
+ const phone = (((_e = t.customer) == null ? void 0 : _e.phone) || t.customer_phone || "").replace(/\s+/g, "").toLowerCase();
611
+ const digitsOnlyPhone = phone.replace(/\D/g, "");
612
+ const brandModel = ([(_f = t.device) == null ? void 0 : _f.brand, (_g = t.device) == null ? void 0 : _g.model_name].filter(Boolean).join(" ") || ((_h = t.custom_fields) == null ? void 0 : _h["Device Model"]) || "").toLowerCase();
613
+ const phoneMatches = phone.includes(q.replace(/\s+/g, "")) || digitsOnlyQ.length >= 4 && (digitsOnlyPhone.includes(digitsOnlyQ) || digitsOnlyQ.includes(digitsOnlyPhone));
614
+ return cleanNum.includes(q) || cleanQ && cleanNum.includes(cleanQ) || displayRef.includes(q) || rawNum.includes(q) || cleanQ && rawNum.includes(cleanQ) || rawId.includes(q) || custName.includes(q) || compName.includes(q) || email.includes(q) || phoneMatches || brandModel.includes(q);
615
+ }).slice(0, 30);
616
+ }, [tickets, query, selectedTicketId]);
617
+ const handleSelect = (ticket) => {
618
+ onSelectTicket(ticket.id);
619
+ setQuery(getTicketLabel(ticket));
620
+ setIsOpen(false);
621
+ };
622
+ const handleClear = () => {
623
+ onClear();
624
+ setQuery("");
625
+ };
626
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative w-full", children: [
627
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
628
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none text-ui-fg-muted", children: /* @__PURE__ */ jsx(MagnifyingGlass, { className: "w-4 h-4" }) }),
629
+ /* @__PURE__ */ jsx(
630
+ "input",
631
+ {
632
+ type: "text",
633
+ value: query,
634
+ onChange: (e) => {
635
+ setQuery(e.target.value);
636
+ setIsOpen(true);
637
+ },
638
+ onFocus: (e) => {
639
+ setIsOpen(true);
640
+ e.target.select();
641
+ },
642
+ placeholder: "Search by customer name, company, or ticket number (e.g. 5390 or RT-5390)...",
643
+ className: "w-full pl-8.5 pr-8 py-1.5 text-xs bg-ui-bg-base border border-ui-border-base rounded-md text-ui-fg-base placeholder:text-ui-fg-muted focus:outline-none focus:ring-1 focus:ring-ui-border-interactive focus:border-ui-border-interactive"
644
+ }
645
+ ),
646
+ (query || selectedTicketId) && /* @__PURE__ */ jsx(
647
+ "button",
648
+ {
649
+ type: "button",
650
+ onClick: handleClear,
651
+ className: "absolute inset-y-0 right-0 pr-2.5 flex items-center text-ui-fg-muted hover:text-ui-fg-base",
652
+ title: "Clear ticket link",
653
+ children: /* @__PURE__ */ jsx(XMark, { className: "w-3.5 h-3.5" })
654
+ }
655
+ )
656
+ ] }),
657
+ isOpen && /* @__PURE__ */ jsx("div", { className: "absolute top-full left-0 right-0 mt-1 z-50 bg-ui-bg-base border border-ui-border-base rounded-md shadow-lg max-h-64 overflow-y-auto divide-y divide-ui-border-base", children: loading ? /* @__PURE__ */ jsxs("div", { className: "p-3 text-xs text-ui-fg-muted text-center flex items-center justify-center gap-2", children: [
658
+ /* @__PURE__ */ jsx(ArrowPath, { className: "w-3 h-3 animate-spin" }),
659
+ "Loading repair tickets..."
660
+ ] }) : filteredTickets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "p-3 text-xs text-ui-fg-muted text-center", children: [
661
+ 'No repair tickets found matching "',
662
+ query,
663
+ '"'
664
+ ] }) : filteredTickets.map((t) => {
665
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
666
+ const cleanNum = String(t.ticket_number || t.id).replace(/^(RT-|JOB-|CERT-)+/i, "");
667
+ const displayRef = `RT-${cleanNum}`;
668
+ const custName = ((_a = t.customer) == null ? void 0 : _a.company_name) || t.company_name || t.customer_name || (((_b = t.customer) == null ? void 0 : _b.first_name) ? `${t.customer.first_name} ${t.customer.last_name || ""}`.trim() : "") || "Customer";
669
+ const compName = ((_c = t.customer) == null ? void 0 : _c.company_name) || t.company_name;
670
+ const brandModel = [(_d = t.device) == null ? void 0 : _d.brand, (_e = t.device) == null ? void 0 : _e.model_name].filter(Boolean).join(" ").trim() || ((_f = t.custom_fields) == null ? void 0 : _f["Device Model"]) || "Device";
671
+ const isSelected = selectedTicketId === t.id;
672
+ return /* @__PURE__ */ jsxs(
673
+ "div",
674
+ {
675
+ onClick: () => handleSelect(t),
676
+ className: `p-2.5 hover:bg-ui-bg-subtle-hover cursor-pointer transition-colors ${isSelected ? "bg-ui-bg-interactive-subtle" : ""}`,
677
+ children: [
678
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 mb-1", children: [
679
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 truncate", children: [
680
+ /* @__PURE__ */ jsx(Badge, { size: "small", color: isSelected ? "blue" : "grey", className: "shrink-0 font-mono", children: displayRef }),
681
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-xs text-ui-fg-base truncate", children: custName }),
682
+ compName && compName !== custName && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-ui-fg-muted truncate", children: [
683
+ "(",
684
+ compName,
685
+ ")"
686
+ ] })
687
+ ] }),
688
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-ui-fg-subtle truncate shrink-0 font-medium", children: brandModel })
689
+ ] }),
690
+ (((_g = t.customer) == null ? void 0 : _g.email) || t.customer_email || ((_h = t.customer) == null ? void 0 : _h.phone) || t.customer_phone) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-ui-fg-muted truncate", children: [
691
+ (((_i = t.customer) == null ? void 0 : _i.email) || t.customer_email) && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
692
+ "📧 ",
693
+ ((_j = t.customer) == null ? void 0 : _j.email) || t.customer_email
694
+ ] }),
695
+ (((_k = t.customer) == null ? void 0 : _k.phone) || t.customer_phone) && /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
696
+ "📞 ",
697
+ ((_l = t.customer) == null ? void 0 : _l.phone) || t.customer_phone
698
+ ] })
699
+ ] })
700
+ ]
701
+ },
702
+ t.id
703
+ );
704
+ }) })
705
+ ] });
706
+ };
401
707
  const DocumentsPage = () => {
402
708
  var _a, _b;
403
709
  const [activeTab, setActiveTab] = useState("hub");
@@ -511,7 +817,7 @@ const DocumentsPage = () => {
511
817
  items: [{ title: "", description: "", quantity: 1, unit_price: 0 }],
512
818
  currency_code: "kes",
513
819
  notes: "",
514
- force_source: "auto",
820
+ force_source: "zoho",
515
821
  auto_send: false
516
822
  });
517
823
  const [customDocForm, setCustomDocForm] = useState({
@@ -521,6 +827,7 @@ const DocumentsPage = () => {
521
827
  customer_name: "Jane Doe",
522
828
  customer_email: "jane.doe@example.com",
523
829
  customer_phone: "+254 712 345 678",
830
+ customer_address: "",
524
831
  body_text: CUSTOM_DOC_PRESETS[0].body_text,
525
832
  items: CUSTOM_DOC_PRESETS[0].items.map((it) => ({ ...it })),
526
833
  custom_fields: { ...CUSTOM_DOC_PRESETS[0].custom_fields || {} },
@@ -534,6 +841,8 @@ const DocumentsPage = () => {
534
841
  const [selectedTicketId, setSelectedTicketId] = useState("");
535
842
  const [newCustomFieldKey, setNewCustomFieldKey] = useState("");
536
843
  const [newCustomFieldValue, setNewCustomFieldValue] = useState("");
844
+ const [customersList, setCustomersList] = useState([]);
845
+ const [loadingCustomers, setLoadingCustomers] = useState(false);
537
846
  const loadRepairTickets = async () => {
538
847
  setLoadingRepairs(true);
539
848
  try {
@@ -551,16 +860,60 @@ const DocumentsPage = () => {
551
860
  setLoadingRepairs(false);
552
861
  }
553
862
  };
863
+ const loadCustomers = async () => {
864
+ setLoadingCustomers(true);
865
+ try {
866
+ const res = await fetch("/admin/customers?limit=100&fields=*addresses", { credentials: "include" });
867
+ if (res.ok) {
868
+ const data = await res.json().catch(() => ({}));
869
+ if (data.customers && Array.isArray(data.customers)) {
870
+ setCustomersList(data.customers);
871
+ return;
872
+ }
873
+ }
874
+ } catch (e) {
875
+ console.warn("Could not fetch /admin/customers:", e);
876
+ } finally {
877
+ setLoadingCustomers(false);
878
+ }
879
+ };
554
880
  useEffect(() => {
555
- if (customDocModalOpen) {
881
+ if (customDocModalOpen || manualModalOpen) {
556
882
  loadRepairTickets();
883
+ loadCustomers();
557
884
  }
558
- }, [customDocModalOpen]);
885
+ }, [customDocModalOpen, manualModalOpen]);
559
886
  const availableTickets = useMemo(() => {
560
- var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j;
887
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
561
888
  const map = /* @__PURE__ */ new Map();
562
889
  for (const t of repairTickets) {
563
- if (t.id) map.set(t.id, t);
890
+ if (t.id) {
891
+ const linkedCust = t.customer_id ? customersList.find((c) => c.id === t.customer_id) : null;
892
+ const custName = ((_a2 = t.customer) == null ? void 0 : _a2.name) || (((_b2 = t.customer) == null ? void 0 : _b2.first_name) ? `${t.customer.first_name} ${t.customer.last_name || ""}`.trim() : "") || t.customer_name || ((_c = t.metadata) == null ? void 0 : _c.customer_name) || (linkedCust ? [linkedCust.first_name, linkedCust.last_name].filter(Boolean).join(" ").trim() : "") || "";
893
+ const compName = ((_d = t.customer) == null ? void 0 : _d.company_name) || t.company_name || ((_e = t.metadata) == null ? void 0 : _e.company_name) || (linkedCust == null ? void 0 : linkedCust.company_name) || "";
894
+ const email = ((_f = t.customer) == null ? void 0 : _f.email) || t.customer_email || ((_g = t.metadata) == null ? void 0 : _g.customer_email) || (linkedCust == null ? void 0 : linkedCust.email) || "";
895
+ const phone = ((_h = t.customer) == null ? void 0 : _h.phone) || t.customer_phone || ((_i = t.metadata) == null ? void 0 : _i.customer_phone) || (linkedCust == null ? void 0 : linkedCust.phone) || "";
896
+ const linkedAddr = (_j = linkedCust == null ? void 0 : linkedCust.addresses) == null ? void 0 : _j[0];
897
+ const formattedCustAddr = linkedAddr ? [linkedAddr.address_1, linkedAddr.address_2, linkedAddr.city, linkedAddr.postal_code, linkedAddr.province, (_k = linkedAddr.country_code) == null ? void 0 : _k.toUpperCase()].filter(Boolean).join(", ") : "";
898
+ const address = ((_l = t.customer) == null ? void 0 : _l.address) || t.customer_address || ((_m = t.metadata) == null ? void 0 : _m.address) || ((_n = t.custom_fields) == null ? void 0 : _n["Address"]) || formattedCustAddr || "";
899
+ map.set(t.id, {
900
+ ...t,
901
+ customer_name: custName || t.customer_name,
902
+ company_name: compName || t.company_name,
903
+ customer_email: email || t.customer_email,
904
+ customer_phone: phone || t.customer_phone,
905
+ customer_address: address || t.customer_address,
906
+ customer: {
907
+ name: custName,
908
+ first_name: (linkedCust == null ? void 0 : linkedCust.first_name) || ((_o = t.customer) == null ? void 0 : _o.first_name),
909
+ last_name: (linkedCust == null ? void 0 : linkedCust.last_name) || ((_p = t.customer) == null ? void 0 : _p.last_name),
910
+ company_name: compName,
911
+ email,
912
+ phone,
913
+ address
914
+ }
915
+ });
916
+ }
564
917
  }
565
918
  for (const d of documents) {
566
919
  const ticketId = d.repair_ticket_id;
@@ -569,14 +922,14 @@ const DocumentsPage = () => {
569
922
  const idKey = ticketId || displayId;
570
923
  if (isRepairDoc && idKey) {
571
924
  if (!map.has(idKey)) {
572
- const customFields = d.custom_fields || ((_a2 = d.metadata) == null ? void 0 : _a2.custom_fields) || {};
925
+ const customFields = d.custom_fields || ((_q = d.metadata) == null ? void 0 : _q.custom_fields) || {};
573
926
  map.set(idKey, {
574
927
  id: idKey,
575
928
  ticket_number: String(displayId).replace(/^(RT-|JOB-|CERT-)+/i, "") || idKey,
576
929
  customer_name: d.customer_name,
577
930
  customer_email: d.customer_email,
578
931
  customer_phone: d.customer_phone,
579
- company_name: ((_b2 = d.customer) == null ? void 0 : _b2.company_name) || ((_c = d.metadata) == null ? void 0 : _c.company_name),
932
+ company_name: ((_r = d.customer) == null ? void 0 : _r.company_name) || ((_s = d.metadata) == null ? void 0 : _s.company_name),
580
933
  device: {
581
934
  brand: customFields["Brand"] || "",
582
935
  model_name: customFields["Device Model"] || customFields["Model"] || "Device",
@@ -587,16 +940,16 @@ const DocumentsPage = () => {
587
940
  technician_name: customFields["Technician"] || "Bonifencia A.",
588
941
  issue_description: customFields["Reported Issue"] || d.notes || "Standard Service",
589
942
  custom_fields: customFields,
590
- items: ((_d = d.metadata) == null ? void 0 : _d.items) || []
943
+ items: ((_t = d.metadata) == null ? void 0 : _t.items) || []
591
944
  });
592
945
  } else {
593
946
  const existing = map.get(idKey);
594
947
  if (!existing.customer_name && d.customer_name) existing.customer_name = d.customer_name;
595
948
  if (!existing.customer_email && d.customer_email) existing.customer_email = d.customer_email;
596
949
  if (!existing.customer_phone && d.customer_phone) existing.customer_phone = d.customer_phone;
597
- const compName = ((_e = d.customer) == null ? void 0 : _e.company_name) || ((_f = d.metadata) == null ? void 0 : _f.company_name);
950
+ const compName = ((_u = d.customer) == null ? void 0 : _u.company_name) || ((_v = d.metadata) == null ? void 0 : _v.company_name);
598
951
  if (!existing.company_name && compName) existing.company_name = compName;
599
- if ((!((_g = existing.device) == null ? void 0 : _g.model_name) || ((_h = existing.device) == null ? void 0 : _h.model_name) === "Device") && ((_j = (_i = d.metadata) == null ? void 0 : _i.custom_fields) == null ? void 0 : _j["Device Model"])) {
952
+ if ((!((_w = existing.device) == null ? void 0 : _w.model_name) || ((_x = existing.device) == null ? void 0 : _x.model_name) === "Device") && ((_z = (_y = d.metadata) == null ? void 0 : _y.custom_fields) == null ? void 0 : _z["Device Model"])) {
600
953
  existing.device = existing.device || {};
601
954
  existing.device.model_name = d.metadata.custom_fields["Device Model"];
602
955
  }
@@ -604,9 +957,87 @@ const DocumentsPage = () => {
604
957
  }
605
958
  }
606
959
  return Array.from(map.values());
607
- }, [repairTickets, documents]);
960
+ }, [repairTickets, documents, customersList]);
961
+ const customerAutofillCandidates = useMemo(() => {
962
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w;
963
+ const map = /* @__PURE__ */ new Map();
964
+ const addCandidate = (c) => {
965
+ var _a3, _b3;
966
+ const key = ((_a3 = c.email) == null ? void 0 : _a3.trim().toLowerCase()) || ((_b3 = c.phone) == null ? void 0 : _b3.trim().replace(/\s+/g, "")) || `${c.name.trim().toLowerCase()}::${c.company_name.trim().toLowerCase()}`;
967
+ if (!key || key === "::" || key === "customer::") return;
968
+ if (!map.has(key)) {
969
+ map.set(key, c);
970
+ } else {
971
+ const existing = map.get(key);
972
+ if (!existing.name || existing.name === "Customer") existing.name = c.name;
973
+ if (!existing.company_name && c.company_name) existing.company_name = c.company_name;
974
+ if (!existing.email && c.email) existing.email = c.email;
975
+ if (!existing.phone && c.phone) existing.phone = c.phone;
976
+ if (!existing.address && c.address) existing.address = c.address;
977
+ }
978
+ };
979
+ for (const c of customersList) {
980
+ const fullName = [c.first_name, c.last_name].filter(Boolean).join(" ").trim();
981
+ const addr = ((_a2 = c.addresses) == null ? void 0 : _a2[0]) || c.default_billing_address || c.billing_address;
982
+ const formattedAddr = addr ? [addr.address_1, addr.address_2, addr.city, addr.postal_code, addr.province, (_b2 = addr.country_code) == null ? void 0 : _b2.toUpperCase()].filter(Boolean).join(", ") : "";
983
+ const company = c.company_name || (addr == null ? void 0 : addr.company) || ((_c = c.metadata) == null ? void 0 : _c.company_name) || "";
984
+ const name = fullName || company || c.email || "Customer";
985
+ addCandidate({
986
+ id: c.id || `cust-${Math.random()}`,
987
+ name,
988
+ company_name: company,
989
+ email: c.email || "",
990
+ phone: c.phone || (addr == null ? void 0 : addr.phone) || "",
991
+ address: formattedAddr,
992
+ source: "customer",
993
+ source_label: "Medusa Customer"
994
+ });
995
+ }
996
+ for (const t of availableTickets) {
997
+ const fullName = (((_d = t.customer) == null ? void 0 : _d.first_name) ? `${t.customer.first_name} ${t.customer.last_name || ""}`.trim() : "") || ((_e = t.customer) == null ? void 0 : _e.name) || t.customer_name || "";
998
+ const company = ((_f = t.customer) == null ? void 0 : _f.company_name) || t.company_name || ((_g = t.metadata) == null ? void 0 : _g.company_name) || "";
999
+ const email = ((_h = t.customer) == null ? void 0 : _h.email) || t.customer_email || ((_i = t.metadata) == null ? void 0 : _i.customer_email) || "";
1000
+ const phone = ((_j = t.customer) == null ? void 0 : _j.phone) || t.customer_phone || ((_k = t.metadata) == null ? void 0 : _k.customer_phone) || "";
1001
+ const address = ((_l = t.customer) == null ? void 0 : _l.address) || t.customer_address || ((_m = t.metadata) == null ? void 0 : _m.address) || ((_n = t.custom_fields) == null ? void 0 : _n["Address"]) || "";
1002
+ const cleanNum = String(t.ticket_number || t.id).replace(/^(RT-|JOB-|CERT-)+/i, "");
1003
+ const name = fullName || company || "Customer";
1004
+ {
1005
+ addCandidate({
1006
+ id: `ticket-${t.id || cleanNum}`,
1007
+ name,
1008
+ company_name: company,
1009
+ email,
1010
+ phone,
1011
+ address,
1012
+ source: "repair",
1013
+ source_label: `Ticket RT-${cleanNum}`
1014
+ });
1015
+ }
1016
+ }
1017
+ for (const d of documents) {
1018
+ const name = d.customer_name || "";
1019
+ const company = ((_o = d.customer) == null ? void 0 : _o.company_name) || ((_p = d.metadata) == null ? void 0 : _p.company_name) || d.company_name || "";
1020
+ const email = d.customer_email || ((_q = d.customer) == null ? void 0 : _q.email) || "";
1021
+ const phone = d.customer_phone || ((_r = d.customer) == null ? void 0 : _r.phone) || "";
1022
+ const address = ((_s = d.customer) == null ? void 0 : _s.address) || d.customer_address || ((_t = d.metadata) == null ? void 0 : _t.customer_address) || ((_v = (_u = d.metadata) == null ? void 0 : _u.customer) == null ? void 0 : _v.address) || "";
1023
+ const docRef = d.display_id || ((_w = d.id) == null ? void 0 : _w.slice(0, 8));
1024
+ if (name || company || email || phone) {
1025
+ addCandidate({
1026
+ id: `doc-${d.id}`,
1027
+ name: name || company || "Customer",
1028
+ company_name: company,
1029
+ email,
1030
+ phone,
1031
+ address,
1032
+ source: "document",
1033
+ source_label: `Doc ${docRef}`
1034
+ });
1035
+ }
1036
+ }
1037
+ return Array.from(map.values());
1038
+ }, [customersList, availableTickets, documents]);
608
1039
  const handleSelectRepairTicket = (ticketId) => {
609
- var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
1040
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C;
610
1041
  setSelectedTicketId(ticketId);
611
1042
  if (!ticketId) return;
612
1043
  const ticket = availableTickets.find((t) => t.id === ticketId || t.ticket_number === ticketId);
@@ -674,12 +1105,14 @@ const DocumentsPage = () => {
674
1105
  });
675
1106
  }
676
1107
  }
1108
+ const customerAddress = ((_A = ticket.customer) == null ? void 0 : _A.address) || ticket.customer_address || ((_B = ticket.metadata) == null ? void 0 : _B.address) || ((_C = ticket.custom_fields) == null ? void 0 : _C["Address"]) || "";
677
1109
  setCustomDocForm((prev) => ({
678
1110
  ...prev,
679
1111
  customer_name: customerName,
680
1112
  customer_email: customerEmail,
681
1113
  customer_phone: customerPhone,
682
1114
  company_name: companyName,
1115
+ customer_address: customerAddress || prev.customer_address,
683
1116
  custom_fields: {
684
1117
  ...prev.custom_fields,
685
1118
  "Device Model": brandModel,
@@ -825,7 +1258,7 @@ const DocumentsPage = () => {
825
1258
  items: validItems,
826
1259
  currency_code: manualForm.currency_code,
827
1260
  notes: manualForm.notes,
828
- force_source: manualForm.force_source === "auto" ? void 0 : manualForm.force_source,
1261
+ force_source: "zoho",
829
1262
  auto_send: manualForm.auto_send
830
1263
  })
831
1264
  });
@@ -844,7 +1277,7 @@ const DocumentsPage = () => {
844
1277
  items: [{ title: "", description: "", quantity: 1, unit_price: 0 }],
845
1278
  currency_code: "kes",
846
1279
  notes: "",
847
- force_source: "auto",
1280
+ force_source: "zoho",
848
1281
  auto_send: false
849
1282
  });
850
1283
  loadDocuments();
@@ -880,7 +1313,8 @@ const DocumentsPage = () => {
880
1313
  name: customDocForm.customer_name,
881
1314
  company_name: customDocForm.company_name || void 0,
882
1315
  email: customDocForm.customer_email,
883
- phone: customDocForm.customer_phone
1316
+ phone: customDocForm.customer_phone,
1317
+ address: customDocForm.customer_address || void 0
884
1318
  },
885
1319
  body_text: customDocForm.body_text,
886
1320
  notes: customDocForm.notes,
@@ -898,6 +1332,7 @@ const DocumentsPage = () => {
898
1332
  customDocForm.send_email_now ? "Custom document generated & emailed successfully!" : "Custom document generated and saved to Documents Hub!"
899
1333
  );
900
1334
  setCustomDocModalOpen(false);
1335
+ setSelectedTicketId("");
901
1336
  loadDocuments();
902
1337
  } else {
903
1338
  alert(`Failed to generate custom document: ${data.message || res.statusText}`);
@@ -2362,52 +2797,6 @@ const DocumentsPage = () => {
2362
2797
  )
2363
2798
  ] })
2364
2799
  ] }),
2365
- /* @__PURE__ */ jsxs("div", { className: "p-5 border border-ui-border-base rounded-lg bg-ui-bg-base flex flex-col justify-between shadow-sm hover:border-ui-border-interactive transition-colors", children: [
2366
- /* @__PURE__ */ jsxs("div", { children: [
2367
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
2368
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2369
- /* @__PURE__ */ jsx("span", { className: "text-xl", children: "💼" }),
2370
- /* @__PURE__ */ jsx(Label, { className: "font-semibold text-sm", children: "Commercial Proforma Quote" })
2371
- ] }),
2372
- /* @__PURE__ */ jsx(Badge, { color: "orange", size: "small", children: "Cost Estimate" })
2373
- ] }),
2374
- /* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle mb-4", children: "Itemized corporate repair quotation with workshop turnaround estimate, OEM part warranties, and 14-day validity period." }),
2375
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
2376
- /* @__PURE__ */ jsx(Label, { className: "text-xs font-medium", children: "Template Style" }),
2377
- /* @__PURE__ */ jsxs(
2378
- Select,
2379
- {
2380
- value: settings.custom_doc_template || "modern",
2381
- onValueChange: (v) => updateSettingField("custom_doc_template", v),
2382
- children: [
2383
- /* @__PURE__ */ jsx(Select.Trigger, { size: "small", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
2384
- /* @__PURE__ */ jsxs(Select.Content, { children: [
2385
- /* @__PURE__ */ jsx(Select.Item, { value: "modern", children: "Modern Minimalist" }),
2386
- /* @__PURE__ */ jsx(Select.Item, { value: "classic", children: "Classic Corporate" }),
2387
- /* @__PURE__ */ jsx(Select.Item, { value: "minimal", children: "Clean Tech Monospace" })
2388
- ] })
2389
- ]
2390
- }
2391
- )
2392
- ] })
2393
- ] }),
2394
- /* @__PURE__ */ jsxs("div", { className: "mt-5 pt-3 border-t border-ui-border-base flex items-center justify-between", children: [
2395
- /* @__PURE__ */ jsx(Text, { className: "text-[11px] text-ui-fg-muted font-mono", children: "Preset: proforma" }),
2396
- /* @__PURE__ */ jsxs(
2397
- Button,
2398
- {
2399
- variant: "secondary",
2400
- size: "small",
2401
- onClick: () => handlePreviewTemplate(settings.custom_doc_template || "modern", "custom", "proforma"),
2402
- isLoading: previewLoading === `${settings.custom_doc_template || "modern"}-custom-proforma`,
2403
- children: [
2404
- /* @__PURE__ */ jsx(ArrowDownTray, { className: "w-3.5 h-3.5 mr-1" }),
2405
- " Preview Proforma"
2406
- ]
2407
- }
2408
- )
2409
- ] })
2410
- ] }),
2411
2800
  /* @__PURE__ */ jsxs("div", { className: "p-5 border border-ui-border-base rounded-lg bg-ui-bg-base flex flex-col justify-between shadow-sm hover:border-ui-border-interactive transition-colors", children: [
2412
2801
  /* @__PURE__ */ jsxs("div", { children: [
2413
2802
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
@@ -3295,6 +3684,23 @@ Your *{document_title} #{display_id}* has been generated.
3295
3684
  }
3296
3685
  )
3297
3686
  ] }),
3687
+ /* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsx(
3688
+ CustomerAutofillCombobox,
3689
+ {
3690
+ candidates: customerAutofillCandidates,
3691
+ loading: loadingCustomers || loadingRepairs,
3692
+ onSelectCustomer: (candidate) => {
3693
+ setManualForm((prev) => ({
3694
+ ...prev,
3695
+ customer_name: candidate.name,
3696
+ company_name: candidate.company_name,
3697
+ customer_email: candidate.email,
3698
+ customer_phone: candidate.phone,
3699
+ customer_address: candidate.address || prev.customer_address
3700
+ }));
3701
+ }
3702
+ }
3703
+ ) }),
3298
3704
  /* @__PURE__ */ jsxs("div", { children: [
3299
3705
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Company Name (Optional)" }),
3300
3706
  /* @__PURE__ */ jsx(
@@ -3308,7 +3714,7 @@ Your *{document_title} #{display_id}* has been generated.
3308
3714
  )
3309
3715
  ] }),
3310
3716
  /* @__PURE__ */ jsxs("div", { children: [
3311
- /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Customer Name" }),
3717
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Customer Name *" }),
3312
3718
  /* @__PURE__ */ jsx(
3313
3719
  Input,
3314
3720
  {
@@ -3343,23 +3749,26 @@ Your *{document_title} #{display_id}* has been generated.
3343
3749
  }
3344
3750
  )
3345
3751
  ] }),
3346
- /* @__PURE__ */ jsxs("div", { children: [
3347
- /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Generation Source" }),
3348
- /* @__PURE__ */ jsxs(
3349
- Select,
3752
+ /* @__PURE__ */ jsxs("div", { className: "col-span-2", children: [
3753
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Customer Address (Optional)" }),
3754
+ /* @__PURE__ */ jsx(
3755
+ Input,
3350
3756
  {
3351
- value: manualForm.force_source,
3352
- onValueChange: (v) => setManualForm({ ...manualForm, force_source: v }),
3353
- children: [
3354
- /* @__PURE__ */ jsx(Select.Trigger, { size: "small", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
3355
- /* @__PURE__ */ jsxs(Select.Content, { children: [
3356
- /* @__PURE__ */ jsx(Select.Item, { value: "auto", children: "Auto (Zoho if enabled, else PDFKit)" }),
3357
- /* @__PURE__ */ jsx(Select.Item, { value: "zoho", children: "Force Zoho Books" }),
3358
- /* @__PURE__ */ jsx(Select.Item, { value: "pdfkit", children: "Force Local PDFKit" })
3359
- ] })
3360
- ]
3757
+ value: manualForm.customer_address,
3758
+ onChange: (e) => setManualForm({ ...manualForm, customer_address: e.target.value }),
3759
+ placeholder: "Street Address, City, Postal Code",
3760
+ size: "small"
3361
3761
  }
3362
3762
  )
3763
+ ] }),
3764
+ /* @__PURE__ */ jsxs("div", { className: "col-span-2", children: [
3765
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Generation Source" }),
3766
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1 px-3 py-2 rounded-md border border-ui-border-base bg-ui-bg-subtle text-xs text-ui-fg-base", children: [
3767
+ /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-emerald-500 animate-pulse" }),
3768
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-ui-fg-base", children: "Zoho Books (Cloud Invoicing)" }),
3769
+ /* @__PURE__ */ jsx(Badge, { size: "small", color: "blue", className: "ml-auto font-mono", children: "Enforced" })
3770
+ ] }),
3771
+ /* @__PURE__ */ jsx(Text, { className: "text-[11px] text-ui-fg-subtle mt-1", children: "All manual invoices and quotes are generated and synced directly via Zoho Books. Local PDFKit fallback is disabled." })
3363
3772
  ] })
3364
3773
  ] }),
3365
3774
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
@@ -3595,29 +4004,14 @@ Your *{document_title} #{display_id}* has been generated.
3595
4004
  ] })
3596
4005
  ] }),
3597
4006
  /* @__PURE__ */ jsx(Text, { className: "text-[11px] text-ui-fg-subtle", children: "Automatically autofills customer details, device specifications (model, serial/IMEI, accessories), technician notes, and services from active repairs." }),
3598
- /* @__PURE__ */ jsxs(
3599
- Select,
4007
+ /* @__PURE__ */ jsx(
4008
+ RepairTicketCombobox,
3600
4009
  {
3601
- value: selectedTicketId || void 0,
3602
- onValueChange: handleSelectRepairTicket,
3603
- children: [
3604
- /* @__PURE__ */ jsx(Select.Trigger, { size: "small", className: "w-full bg-ui-bg-base", children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "-- Select a Repair Ticket to autofill fields --" }) }),
3605
- /* @__PURE__ */ jsx(Select.Content, { children: availableTickets.map((t) => {
3606
- var _a2, _b2, _c, _d, _e;
3607
- const brandModel = [(_a2 = t.device) == null ? void 0 : _a2.brand, (_b2 = t.device) == null ? void 0 : _b2.model_name].filter(Boolean).join(" ").trim() || ((_c = t.custom_fields) == null ? void 0 : _c["Device Model"]) || "Device";
3608
- const cleanNum = String(t.ticket_number || t.id).replace(/^(RT-|JOB-)+/i, "");
3609
- const displayRef = `RT-${cleanNum}`;
3610
- const custName = ((_d = t.customer) == null ? void 0 : _d.company_name) || t.company_name || t.customer_name || (((_e = t.customer) == null ? void 0 : _e.first_name) ? `${t.customer.first_name} ${t.customer.last_name || ""}`.trim() : "") || "Customer";
3611
- return /* @__PURE__ */ jsxs(Select.Item, { value: t.id, children: [
3612
- displayRef,
3613
- " — ",
3614
- custName,
3615
- " (",
3616
- brandModel,
3617
- ")"
3618
- ] }, t.id);
3619
- }) })
3620
- ]
4010
+ tickets: availableTickets,
4011
+ selectedTicketId,
4012
+ onSelectTicket: handleSelectRepairTicket,
4013
+ onClear: handleClearRepairLink,
4014
+ loading: loadingRepairs
3621
4015
  }
3622
4016
  ),
3623
4017
  selectedTicketId && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1.5 rounded bg-ui-bg-interactive-subtle border border-ui-border-interactive text-[11px] text-ui-fg-interactive font-medium", children: [
@@ -3665,6 +4059,23 @@ Your *{document_title} #{display_id}* has been generated.
3665
4059
  }
3666
4060
  )
3667
4061
  ] }),
4062
+ /* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsx(
4063
+ CustomerAutofillCombobox,
4064
+ {
4065
+ candidates: customerAutofillCandidates,
4066
+ loading: loadingCustomers || loadingRepairs,
4067
+ onSelectCustomer: (candidate) => {
4068
+ setCustomDocForm((prev) => ({
4069
+ ...prev,
4070
+ customer_name: candidate.name,
4071
+ company_name: candidate.company_name,
4072
+ customer_email: candidate.email,
4073
+ customer_phone: candidate.phone,
4074
+ customer_address: candidate.address || prev.customer_address
4075
+ }));
4076
+ }
4077
+ }
4078
+ ) }),
3668
4079
  /* @__PURE__ */ jsxs("div", { children: [
3669
4080
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Company Name (Optional)" }),
3670
4081
  /* @__PURE__ */ jsx(
@@ -3712,6 +4123,18 @@ Your *{document_title} #{display_id}* has been generated.
3712
4123
  size: "small"
3713
4124
  }
3714
4125
  )
4126
+ ] }),
4127
+ /* @__PURE__ */ jsxs("div", { className: "col-span-2", children: [
4128
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Customer Address (Optional)" }),
4129
+ /* @__PURE__ */ jsx(
4130
+ Input,
4131
+ {
4132
+ value: customDocForm.customer_address,
4133
+ onChange: (e) => setCustomDocForm({ ...customDocForm, customer_address: e.target.value }),
4134
+ placeholder: "Street Address, City, Postal Code",
4135
+ size: "small"
4136
+ }
4137
+ )
3715
4138
  ] })
3716
4139
  ] }),
3717
4140
  /* @__PURE__ */ jsxs("div", { children: [