medusajs-documents-plugin 1.1.32 → 1.1.34
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.
- package/.medusa/server/src/admin/index.js +510 -84
- package/.medusa/server/src/admin/index.mjs +510 -84
- package/.medusa/server/src/api/admin/documents/templates/preview/route.js +104 -34
- package/.medusa/server/src/api/documents/templates/preview/route.js +104 -34
- package/.medusa/server/src/services/pdfkit-generator.js +218 -48
- package/CHANGELOG.md +10 -0
- package/package.json +1 -1
|
@@ -210,27 +210,23 @@ const CUSTOM_DOC_PRESETS = [
|
|
|
210
210
|
badge: "90-Day Coverage",
|
|
211
211
|
title: "Warranty Certificate",
|
|
212
212
|
subject: "Your Official Warranty Certificate - Urban Device Care",
|
|
213
|
-
body_text: `This
|
|
213
|
+
body_text: `This official Warranty Certificate certifies that the serviced hardware specified below has undergone complete technical overhaul, component replacement, and bench testing in accordance with manufacturer specifications.
|
|
214
214
|
|
|
215
|
-
All
|
|
215
|
+
All genuine replacement components and associated technical workshop labor are guaranteed against functional defects for a period of 90 days from the date of issue. Urban Device Care warrants that all installed modules have passed rigorous electrical load and multi-point quality assurance protocols.`,
|
|
216
216
|
items: [
|
|
217
|
-
{ title: "OEM
|
|
218
|
-
{ title: "
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
{ title: "Logic Board & Power Rail Inspection", description: "Voltage drop and circuit continuity tested", quantity: 1, unit_price: 0 },
|
|
231
|
-
{ title: "Display & Touch Digitizer Diagnostic", description: "Digitizer touch grid, TrueTone, and Ambient Sensor test", quantity: 1, unit_price: 0 },
|
|
232
|
-
{ title: "Battery Health & Thermal Profiling", description: "Cycle count, capacity retention, and charge testing", quantity: 1, unit_price: 0 }
|
|
233
|
-
]
|
|
217
|
+
{ title: "OEM Screen Assembly & Digitizer Replacement", description: "90-Day Comprehensive Warranty Coverage against touch and display anomalies", quantity: 1, unit_price: 0 },
|
|
218
|
+
{ title: "Internal Power Management & Battery Calibration", description: "Warranty against capacity degradation and voltage fluctuations", quantity: 1, unit_price: 0 },
|
|
219
|
+
{ title: "Clean-Room Workshop Installation & Bench Clearance", description: "Certified labor coverage and water/dust ingress seal integrity", quantity: 1, unit_price: 0 }
|
|
220
|
+
],
|
|
221
|
+
custom_fields: {
|
|
222
|
+
"Device Model": "Apple iPhone 15 Pro Max (256GB - Natural Titanium)",
|
|
223
|
+
"Serial / IMEI": "F2LZ90K8MD6R",
|
|
224
|
+
"Ticket Reference": "RT-2024-8841",
|
|
225
|
+
"Technician": "Bonifencia A. (Lead Certified Engineer)",
|
|
226
|
+
"Coverage Period": "90 Days from Date of Collection",
|
|
227
|
+
"Warranty Status": "Active & Validated"
|
|
228
|
+
},
|
|
229
|
+
notes: "Warranty excludes accidental damage, liquid ingress, unauthorized third-party tampering, or software jailbreaking. Please retain this document and quote your ticket reference number for any warranty claims."
|
|
234
230
|
},
|
|
235
231
|
{
|
|
236
232
|
id: "service_completion",
|
|
@@ -238,12 +234,72 @@ All hardware replacements and associated workshop labor are covered under our co
|
|
|
238
234
|
icon: "📜",
|
|
239
235
|
badge: "QC Cleared",
|
|
240
236
|
title: "Certificate of Service Completion",
|
|
241
|
-
subject: "Service Completion & Quality
|
|
242
|
-
body_text: `This
|
|
237
|
+
subject: "Certificate of Service Completion & Quality Sign-Off",
|
|
238
|
+
body_text: `This Certificate of Service Completion verifies that all diagnostic, repair, and maintenance procedures authorized under the referenced work order have been satisfactorily executed in full adherence to ISO/IPC hardware rework standards.
|
|
239
|
+
|
|
240
|
+
The equipment has successfully cleared all pre-delivery functional verifications, thermal load evaluations, and electrical safety diagnostics, and is officially released as fully operational.`,
|
|
241
|
+
items: [
|
|
242
|
+
{ title: "Hardware Overhaul & Micro-Soldering Service", description: "Replaced degraded SMD power filtering capacitors and restored logic board rails", quantity: 1, unit_price: 0 },
|
|
243
|
+
{ title: "Firmware Diagnostics & Peripheral Bench Testing", description: "100% operational clearance on audio codecs, cameras, biometric sensors, and radios", quantity: 1, unit_price: 0 },
|
|
244
|
+
{ title: "Acoustic Chamber & Thermal Stress Verification", description: "Pass: Maximum load temperature maintained within normal threshold (38°C)", quantity: 1, unit_price: 0 }
|
|
245
|
+
],
|
|
246
|
+
custom_fields: {
|
|
247
|
+
"Device Model": "MacBook Pro 16-inch (M2 Max - Space Grey)",
|
|
248
|
+
"Serial / IMEI": "C02G8027MD6T",
|
|
249
|
+
"Ticket Reference": "RT-2024-9104",
|
|
250
|
+
"Technician": "Bonifencia A. (Lead Certified Engineer)",
|
|
251
|
+
"Quality Control Inspector": "Kevin M. (Senior QA Specialist)",
|
|
252
|
+
"Final Testing Result": "PASSED - All Benchmarks Satisfied"
|
|
253
|
+
},
|
|
254
|
+
notes: "All original customer data was preserved intact. Device firmware updated to current stable release. The client has 7 days from collection to report any cosmetic or configuration discrepancies."
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: "destruction",
|
|
258
|
+
name: "Certificate of Destruction & Disposal",
|
|
259
|
+
icon: "♻️",
|
|
260
|
+
badge: "Eco & Compliance",
|
|
261
|
+
title: "Certificate of Destruction & Eco-Disposal",
|
|
262
|
+
subject: "Certificate of Secure Hardware Destruction & Eco-Disposal",
|
|
263
|
+
body_text: `This Certificate of Destruction legally certifies that the decommissioned computing equipment and electronic media detailed herein have been securely and irrevocably destroyed in compliance with NIST SP 800-88 Rev. 1 guidelines and WEEE environmental disposal regulations.
|
|
264
|
+
|
|
265
|
+
All storage media have undergone physical de-manufacturing, mechanical shredding, and complete demagnetization, ensuring zero residual data retrievability. Non-hazardous materials have been transferred to certified green-reclamation facilities.`,
|
|
266
|
+
items: [
|
|
267
|
+
{ title: "Physical Demanufacturing & High-Torque Shredding", description: "Particle size reduced below DIN 66399 Level H-5 security standards", quantity: 1, unit_price: 0 },
|
|
268
|
+
{ title: "Certified NIST 800-88 Cryptographic & Magnetic Sanitization", description: "Complete data eradication across all solid-state and magnetic platters", quantity: 1, unit_price: 0 },
|
|
269
|
+
{ title: "Hazardous Materials & Lithium Battery Eco-Reclamation", description: "Zero-landfill certified chemical neutralization and precious metal recovery", quantity: 1, unit_price: 0 }
|
|
270
|
+
],
|
|
271
|
+
custom_fields: {
|
|
272
|
+
"Device Model": "Dell PowerEdge R740 Server / 4x 1.92TB NVMe",
|
|
273
|
+
"Serial / IMEI": "SN-DELL-882194-X",
|
|
274
|
+
"Ticket Reference": "RT-2024-7712",
|
|
275
|
+
"Destruction Method": "Physical Shear Shredding & Degaussing",
|
|
276
|
+
"Compliance Standard": "NIST SP 800-88 Rev. 1 & WEEE Directive",
|
|
277
|
+
"Authorized Disposal Officer": "Bonifencia A. (Certified Waste Handler)"
|
|
278
|
+
},
|
|
279
|
+
notes: "Urban Device Care Ltd indemnifies the client against data breach liabilities originating from the specified serialized assets following the verified execution of destruction protocols."
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "diagnostic",
|
|
283
|
+
name: "Diagnostic & Inspection Report",
|
|
284
|
+
icon: "🔍",
|
|
285
|
+
badge: "Hardware Findings",
|
|
286
|
+
title: "Diagnostic & Hardware Inspection Report",
|
|
287
|
+
subject: "Device Diagnostic & Inspection Findings - Urban Device Care",
|
|
288
|
+
body_text: `Our certified repair specialists have concluded an in-depth hardware, firmware, and electrical diagnostic examination of your device. The comprehensive technical findings, voltage line readings, and component health metrics are detailed below alongside our certified remediation roadmap.`,
|
|
243
289
|
items: [
|
|
244
|
-
{ title: "
|
|
245
|
-
{ title: "
|
|
246
|
-
|
|
290
|
+
{ title: "Logic Board Primary Power Rail Diagnostic", description: "PP_VDD_MAIN measured at 3.82V (Nominal). No ground short-circuits detected on primary PMIC", quantity: 1, unit_price: 0 },
|
|
291
|
+
{ title: "Display Digitizer & Touch Array Profiling", description: "Matrix micro-fracture identified in bottom-left corner causing erratic capacitive touch feedback", quantity: 1, unit_price: 0 },
|
|
292
|
+
{ title: "Battery Health & Internal Impedance Evaluation", description: "Current capacity: 74% of design capacity (842 cycles). Service battery recommended", quantity: 1, unit_price: 0 }
|
|
293
|
+
],
|
|
294
|
+
custom_fields: {
|
|
295
|
+
"Device Model": "Samsung Galaxy S23 Ultra (512GB - Phantom Black)",
|
|
296
|
+
"Serial / IMEI": "358941092847120",
|
|
297
|
+
"Ticket Reference": "RT-2024-6523",
|
|
298
|
+
"Technician": "Bonifencia A. (Lead Certified Engineer)",
|
|
299
|
+
"Diagnostic Verdict": "Component Replacement Feasible",
|
|
300
|
+
"Estimated Labor Time": "2.5 Workshop Hours"
|
|
301
|
+
},
|
|
302
|
+
notes: "Diagnostic findings remain valid for 14 calendar days. Internal safety seals have been inspected and logged."
|
|
247
303
|
},
|
|
248
304
|
{
|
|
249
305
|
id: "handover",
|
|
@@ -252,11 +308,23 @@ All hardware replacements and associated workshop labor are covered under our co
|
|
|
252
308
|
badge: "Custody Form",
|
|
253
309
|
title: "Equipment Handover & Custody Release Agreement",
|
|
254
310
|
subject: "Equipment Handover & Custody Release Form",
|
|
255
|
-
body_text: `This
|
|
311
|
+
body_text: `This Handover Agreement legally documents the transfer of custody of the repaired equipment and accessories listed below from Urban Device Care to the client or authorized corporate representative.
|
|
312
|
+
|
|
313
|
+
The recipient confirms that the device has been inspected, tested in their presence (including display, camera, audio, and network connectivity), and accepted in verified good working condition.`,
|
|
256
314
|
items: [
|
|
257
|
-
{ title: "
|
|
258
|
-
{ title: "
|
|
259
|
-
|
|
315
|
+
{ title: "Primary Computing Device Release", description: "Serial verified, cosmetic inspection performed, customer account signed in", quantity: 1, unit_price: 0 },
|
|
316
|
+
{ title: "OEM 96W USB-C Power Adapter & Braided Cable", description: "Tested and verified delivering 20V/4.8A under load", quantity: 1, unit_price: 0 },
|
|
317
|
+
{ title: "Protective Hard Case & Accessories", description: "Returned in clean, intact custody state", quantity: 1, unit_price: 0 }
|
|
318
|
+
],
|
|
319
|
+
custom_fields: {
|
|
320
|
+
"Device Model": "Lenovo ThinkPad X1 Carbon Gen 10",
|
|
321
|
+
"Serial / IMEI": "PF-29X810-22",
|
|
322
|
+
"Ticket Reference": "RT-2024-5390",
|
|
323
|
+
"Released By": "Bonifencia A. (Urban Device Care)",
|
|
324
|
+
"Received By": "Authorized Client Representative",
|
|
325
|
+
"Handover Condition": "Inspected & Approved in Workshop"
|
|
326
|
+
},
|
|
327
|
+
notes: "By accepting handover, the client acknowledges satisfactory return of all custody items and confirms device operability."
|
|
260
328
|
},
|
|
261
329
|
{
|
|
262
330
|
id: "proforma",
|
|
@@ -264,12 +332,24 @@ All hardware replacements and associated workshop labor are covered under our co
|
|
|
264
332
|
icon: "💼",
|
|
265
333
|
badge: "Cost Estimate",
|
|
266
334
|
title: "Commercial Quotation (Proforma)",
|
|
267
|
-
subject: "Commercial Quotation & Scope of Work",
|
|
268
|
-
body_text: `Thank you for choosing Urban Device Care. Below is an itemized
|
|
335
|
+
subject: "Commercial Quotation & Scope of Work - Urban Device Care",
|
|
336
|
+
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.
|
|
337
|
+
|
|
338
|
+
This quotation is valid for 14 calendar days from the date of issue. Upon formal approval, component procurement and bench allocation will commence immediately.`,
|
|
269
339
|
items: [
|
|
270
|
-
{ title: "OEM
|
|
271
|
-
{ title: "Workshop
|
|
272
|
-
|
|
340
|
+
{ title: "OEM Liquid Retina XDR Display Assembly", description: "Brand new original factory panel with factory color calibration", quantity: 1, unit_price: 42e3 },
|
|
341
|
+
{ title: "Clean-Room Workshop Installation & Sensor Alignment", description: "Certified clean-room installation, gasket replacement & TrueTone calibration", quantity: 1, unit_price: 6500 },
|
|
342
|
+
{ title: "Hydrophobic Dust & Water Ingress Seal Restoration", description: "OEM adhesive barrier pressure cured to IP68 rating", quantity: 1, unit_price: 2500 }
|
|
343
|
+
],
|
|
344
|
+
custom_fields: {
|
|
345
|
+
"Device Model": "Apple iPad Pro 12.9-inch (6th Gen - Wi-Fi + Cellular)",
|
|
346
|
+
"Serial / IMEI": "DLXW9201M93K",
|
|
347
|
+
"Ticket Reference": "RT-2024-4190",
|
|
348
|
+
"Estimator / Technician": "Bonifencia A.",
|
|
349
|
+
"Validity Period": "14 Calendar Days",
|
|
350
|
+
"Estimated Turnaround": "24 - 48 Hours"
|
|
351
|
+
},
|
|
352
|
+
notes: "All replaced hardware carries our standard 90-day warranty coverage. Payment accepted via M-Pesa, Bank Transfer, or Credit/Debit Card."
|
|
273
353
|
},
|
|
274
354
|
{
|
|
275
355
|
id: "trade_in",
|
|
@@ -278,24 +358,22 @@ All hardware replacements and associated workshop labor are covered under our co
|
|
|
278
358
|
badge: "Trade-In & Transfer",
|
|
279
359
|
title: "Device Trade-In & Buyback Agreement",
|
|
280
360
|
subject: "Device Trade-In & Buyback Agreement - Urban Device Care",
|
|
281
|
-
body_text: `This
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{ title: "Data Sanitization & Ownership Transfer", description: "Certified NIST 800-88 cryptographic wipe", quantity: 1, unit_price: 0 }
|
|
285
|
-
]
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
id: "destruction",
|
|
289
|
-
name: "Certificate of Destruction & Disposal",
|
|
290
|
-
icon: "♻️",
|
|
291
|
-
badge: "Eco & Compliance",
|
|
292
|
-
title: "Certificate of Destruction & Eco-Disposal",
|
|
293
|
-
subject: "Certificate of Hardware Destruction & Eco-Disposal",
|
|
294
|
-
body_text: `This certificate confirms that the decommissioned hardware listed below has been safely demanufactured, shredded, or recycled in full compliance with WEEE and environmental data destruction standards.`,
|
|
361
|
+
body_text: `This Agreement formalizes the title transfer, functional evaluation, and agreed trade-in credit valuation for the customer-owned equipment listed below.
|
|
362
|
+
|
|
363
|
+
The seller warrants that they are the sole lawful owner of the equipment, that it is free of all liens, financial encumbrances, iCloud/Google activation locks, and corporate MDM profiles. Urban Device Care agrees to apply the confirmed credit towards service fees or disbursement.`,
|
|
295
364
|
items: [
|
|
296
|
-
{ title: "
|
|
297
|
-
{ title: "
|
|
298
|
-
]
|
|
365
|
+
{ title: "Trade-In Device Valuation Allowance (Grade A)", description: "Full functional clearance: battery health 91%, minor bezel wear, pristine screen", quantity: 1, unit_price: 35e3 },
|
|
366
|
+
{ title: "NIST 800-88 Cryptographic Data Sanitation Protocol", description: "Certified factory reset and firmware wipe with zero data retention guarantee", quantity: 1, unit_price: 0 }
|
|
367
|
+
],
|
|
368
|
+
custom_fields: {
|
|
369
|
+
"Device Model": "Apple iPhone 13 Pro (128GB - Sierra Blue)",
|
|
370
|
+
"Serial / IMEI": "G6TD9021ML8R",
|
|
371
|
+
"Ticket Reference": "RT-2024-3829",
|
|
372
|
+
"Activation Lock": "Removed & Verified Off",
|
|
373
|
+
"Assessed Grade": "Grade A (Excellent)",
|
|
374
|
+
"Evaluation Specialist": "Bonifencia A."
|
|
375
|
+
},
|
|
376
|
+
notes: "Ownership transfers immediately upon document execution. Valuation is binding and irrevocable once trade-in payout or store credit is issued."
|
|
299
377
|
},
|
|
300
378
|
{
|
|
301
379
|
id: "blank",
|
|
@@ -304,13 +382,21 @@ All hardware replacements and associated workshop labor are covered under our co
|
|
|
304
382
|
badge: "Custom",
|
|
305
383
|
title: "Official Document",
|
|
306
384
|
subject: "Official Document from Urban Device Care",
|
|
307
|
-
body_text: "",
|
|
385
|
+
body_text: "This official technical document confirms the specifications, service terms, and verified conditions documented by Urban Device Care for the specified customer asset.",
|
|
308
386
|
items: [
|
|
309
|
-
{ title: "", description: "", quantity: 1, unit_price: 0 }
|
|
310
|
-
]
|
|
387
|
+
{ title: "Professional Hardware Inspection & Maintenance Service", description: "Certified technical evaluation and service clearance", quantity: 1, unit_price: 0 }
|
|
388
|
+
],
|
|
389
|
+
custom_fields: {
|
|
390
|
+
"Device Model": "Computing Asset / Hardware Module",
|
|
391
|
+
"Serial / IMEI": "SN-XXXX-XXXX",
|
|
392
|
+
"Ticket Reference": "RT-2024-0001",
|
|
393
|
+
"Technician": "Bonifencia A."
|
|
394
|
+
},
|
|
395
|
+
notes: "Standard terms and conditions apply. For inquiries, contact info@urbandevicecare.co.uk."
|
|
311
396
|
}
|
|
312
397
|
];
|
|
313
398
|
const DocumentsPage = () => {
|
|
399
|
+
var _a, _b;
|
|
314
400
|
const [activeTab, setActiveTab] = useState("hub");
|
|
315
401
|
const [documents, setDocuments] = useState([]);
|
|
316
402
|
const [loading, setLoading] = useState(true);
|
|
@@ -411,32 +497,203 @@ const DocumentsPage = () => {
|
|
|
411
497
|
});
|
|
412
498
|
const [customDocForm, setCustomDocForm] = useState({
|
|
413
499
|
preset_id: "warranty",
|
|
414
|
-
title:
|
|
500
|
+
title: CUSTOM_DOC_PRESETS[0].title,
|
|
415
501
|
company_name: "",
|
|
416
|
-
customer_name: "",
|
|
417
|
-
customer_email: "",
|
|
418
|
-
customer_phone: "",
|
|
419
|
-
body_text:
|
|
420
|
-
items: [
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
],
|
|
502
|
+
customer_name: "Jane Doe",
|
|
503
|
+
customer_email: "jane.doe@example.com",
|
|
504
|
+
customer_phone: "+254 712 345 678",
|
|
505
|
+
body_text: CUSTOM_DOC_PRESETS[0].body_text,
|
|
506
|
+
items: CUSTOM_DOC_PRESETS[0].items.map((it) => ({ ...it })),
|
|
507
|
+
custom_fields: { ...CUSTOM_DOC_PRESETS[0].custom_fields || {} },
|
|
508
|
+
notes: CUSTOM_DOC_PRESETS[0].notes || "",
|
|
424
509
|
currency_code: "kes",
|
|
425
510
|
send_email_now: true,
|
|
426
|
-
email_subject:
|
|
511
|
+
email_subject: CUSTOM_DOC_PRESETS[0].subject
|
|
427
512
|
});
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
513
|
+
const [repairTickets, setRepairTickets] = useState([]);
|
|
514
|
+
const [loadingRepairs, setLoadingRepairs] = useState(false);
|
|
515
|
+
const [selectedTicketId, setSelectedTicketId] = useState("");
|
|
516
|
+
const [newCustomFieldKey, setNewCustomFieldKey] = useState("");
|
|
517
|
+
const [newCustomFieldValue, setNewCustomFieldValue] = useState("");
|
|
518
|
+
const loadRepairTickets = async () => {
|
|
519
|
+
setLoadingRepairs(true);
|
|
520
|
+
try {
|
|
521
|
+
const res = await fetch("/admin/repairs", { credentials: "include" });
|
|
522
|
+
if (res.ok) {
|
|
523
|
+
const data = await res.json().catch(() => ({}));
|
|
524
|
+
if (data.repair_tickets && Array.isArray(data.repair_tickets)) {
|
|
525
|
+
setRepairTickets(data.repair_tickets);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} catch (e) {
|
|
530
|
+
console.warn("Could not fetch /admin/repairs:", e);
|
|
531
|
+
} finally {
|
|
532
|
+
setLoadingRepairs(false);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
if (customDocModalOpen) {
|
|
537
|
+
loadRepairTickets();
|
|
538
|
+
}
|
|
539
|
+
}, [customDocModalOpen]);
|
|
540
|
+
const availableTickets = useMemo(() => {
|
|
541
|
+
var _a2, _b2;
|
|
542
|
+
const map = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const t of repairTickets) {
|
|
544
|
+
if (t.id) map.set(t.id, t);
|
|
545
|
+
}
|
|
546
|
+
for (const d of documents) {
|
|
547
|
+
const ticketId = d.repair_ticket_id;
|
|
548
|
+
const displayId = d.display_id || "";
|
|
549
|
+
const isRepairDoc = displayId.startsWith("RT-") || displayId.startsWith("JOB-") || Boolean(ticketId);
|
|
550
|
+
const idKey = ticketId || displayId;
|
|
551
|
+
if (isRepairDoc && idKey && !map.has(idKey)) {
|
|
552
|
+
const customFields = d.custom_fields || ((_a2 = d.metadata) == null ? void 0 : _a2.custom_fields) || {};
|
|
553
|
+
map.set(idKey, {
|
|
554
|
+
id: idKey,
|
|
555
|
+
ticket_number: String(displayId).replace(/^(RT-|JOB-)+/i, "") || idKey,
|
|
556
|
+
customer_name: d.customer_name,
|
|
557
|
+
customer_email: d.customer_email,
|
|
558
|
+
customer_phone: d.customer_phone,
|
|
559
|
+
device: {
|
|
560
|
+
brand: customFields["Brand"] || "",
|
|
561
|
+
model_name: customFields["Device Model"] || customFields["Model"] || "Device",
|
|
562
|
+
serial_number: customFields["Serial / IMEI"] || customFields["Serial Number"] || "N/A",
|
|
563
|
+
condition: customFields["Condition"] || "Good condition"
|
|
564
|
+
},
|
|
565
|
+
accessories: customFields["Accessories"] || "None",
|
|
566
|
+
technician_name: customFields["Technician"] || "Bonifencia A.",
|
|
567
|
+
issue_description: customFields["Reported Issue"] || d.notes || "Standard Service",
|
|
568
|
+
custom_fields: customFields,
|
|
569
|
+
items: ((_b2 = d.metadata) == null ? void 0 : _b2.items) || []
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return Array.from(map.values());
|
|
574
|
+
}, [repairTickets, documents]);
|
|
575
|
+
const handleSelectRepairTicket = (ticketId) => {
|
|
576
|
+
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
|
|
577
|
+
setSelectedTicketId(ticketId);
|
|
578
|
+
if (!ticketId) return;
|
|
579
|
+
const ticket = availableTickets.find((t) => t.id === ticketId || t.ticket_number === ticketId);
|
|
580
|
+
if (!ticket) return;
|
|
581
|
+
const brandModel = [(_a2 = ticket.device) == null ? void 0 : _a2.brand, (_b2 = ticket.device) == null ? void 0 : _b2.model_name].filter(Boolean).join(" ").trim() || ((_c = ticket.custom_fields) == null ? void 0 : _c["Device Model"]) || "Apple iPhone 15 Pro Max";
|
|
582
|
+
const serialImei = ((_d = ticket.device) == null ? void 0 : _d.serial_number) || ((_e = ticket.device) == null ? void 0 : _e.imei) || ((_f = ticket.custom_fields) == null ? void 0 : _f["Serial / IMEI"]) || "N/A";
|
|
583
|
+
const technician = ticket.technician_name || ((_g = ticket.custom_fields) == null ? void 0 : _g["Technician"]) || "Bonifencia A. (Lead Certified Engineer)";
|
|
584
|
+
const accessories = ticket.accessories || ((_h = ticket.custom_fields) == null ? void 0 : _h["Accessories"]) || "None (Unit only)";
|
|
585
|
+
const condition = ((_i = ticket.device) == null ? void 0 : _i.condition) || ((_j = ticket.custom_fields) == null ? void 0 : _j["Condition"]) || "Good (Normal wear)";
|
|
586
|
+
const cleanNum = String(ticket.ticket_number || ticket.id).replace(/^(RT-|JOB-)+/i, "");
|
|
587
|
+
const ticketRef = `RT-${cleanNum}`;
|
|
588
|
+
const customerName = ((_k = ticket.customer) == null ? void 0 : _k.company_name) || ticket.company_name || ticket.customer_name || (((_l = ticket.customer) == null ? void 0 : _l.first_name) ? `${ticket.customer.first_name} ${ticket.customer.last_name || ""}`.trim() : "") || "Valued Customer";
|
|
589
|
+
const customerEmail = ((_m = ticket.customer) == null ? void 0 : _m.email) || ticket.customer_email || "";
|
|
590
|
+
const customerPhone = ((_n = ticket.customer) == null ? void 0 : _n.phone) || ticket.customer_phone || "";
|
|
591
|
+
const companyName = ((_o = ticket.customer) == null ? void 0 : _o.company_name) || ticket.company_name || "";
|
|
592
|
+
const ticketItems = [];
|
|
593
|
+
if (ticket.product_variants && Array.isArray(ticket.product_variants) && ticket.product_variants.length > 0) {
|
|
594
|
+
for (const pv of ticket.product_variants) {
|
|
595
|
+
const qty = Number(pv.quantity || ((_p = pv.pivot) == null ? void 0 : _p.quantity) || 1);
|
|
596
|
+
const unitPrice = Number(pv.unit_price ?? pv.price ?? ((_r = (_q = pv.prices) == null ? void 0 : _q[0]) == null ? void 0 : _r.amount) ?? 0);
|
|
597
|
+
const partTitle = ((_s = pv.product) == null ? void 0 : _s.title) ? `${pv.product.title}${pv.title && pv.title !== "Default Variant" ? ` (${pv.title})` : ""}` : pv.title || ((_t = pv.product) == null ? void 0 : _t.title) || "Inventory Part";
|
|
598
|
+
ticketItems.push({
|
|
599
|
+
title: partTitle,
|
|
600
|
+
description: pv.sku ? `SKU: ${pv.sku}` : "",
|
|
601
|
+
quantity: qty,
|
|
602
|
+
unit_price: unitPrice
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (ticket.custom_parts && Array.isArray(ticket.custom_parts) && ticket.custom_parts.length > 0) {
|
|
607
|
+
for (const cp of ticket.custom_parts) {
|
|
608
|
+
ticketItems.push({
|
|
609
|
+
title: cp.name || cp.title || "Custom Repair Part / Service",
|
|
610
|
+
description: cp.description || "",
|
|
611
|
+
quantity: Number(cp.quantity || 1),
|
|
612
|
+
unit_price: Number(cp.price || cp.unit_price || 0)
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (((_u = ticket.device) == null ? void 0 : _u.parts_used) && Array.isArray(ticket.device.parts_used)) {
|
|
617
|
+
for (const p of ticket.device.parts_used) {
|
|
618
|
+
ticketItems.push({
|
|
619
|
+
title: p.name || p.title || "Replacement Part",
|
|
620
|
+
description: p.sku ? `SKU: ${p.sku}` : "",
|
|
621
|
+
quantity: Number(p.quantity || 1),
|
|
622
|
+
unit_price: Number(p.price || 0)
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (Number(ticket.labor_estimate || 0) > 0) {
|
|
627
|
+
ticketItems.push({
|
|
628
|
+
title: "Workshop Labor & Clean-Room Installation",
|
|
629
|
+
description: "Certified hardware diagnostic and component installation",
|
|
630
|
+
quantity: 1,
|
|
631
|
+
unit_price: Number(ticket.labor_estimate)
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
if (ticketItems.length === 0 && ticket.items && Array.isArray(ticket.items) && ticket.items.length > 0) {
|
|
635
|
+
for (const it of ticket.items) {
|
|
636
|
+
ticketItems.push({
|
|
637
|
+
title: it.title || "Service Item",
|
|
638
|
+
description: it.description || "",
|
|
639
|
+
quantity: Number(it.quantity || 1),
|
|
640
|
+
unit_price: Number(it.unit_price || 0)
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
431
644
|
setCustomDocForm((prev) => ({
|
|
432
645
|
...prev,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
646
|
+
customer_name: customerName,
|
|
647
|
+
customer_email: customerEmail,
|
|
648
|
+
customer_phone: customerPhone,
|
|
649
|
+
company_name: companyName,
|
|
650
|
+
custom_fields: {
|
|
651
|
+
...prev.custom_fields,
|
|
652
|
+
"Device Model": brandModel,
|
|
653
|
+
"Serial / IMEI": serialImei,
|
|
654
|
+
"Ticket Reference": ticketRef,
|
|
655
|
+
"Technician": technician,
|
|
656
|
+
"Accessories": accessories,
|
|
657
|
+
"Condition": condition,
|
|
658
|
+
"Reported Issue": ticket.issue_description || ticket.reported_issue || "Standard Service"
|
|
659
|
+
},
|
|
660
|
+
items: ticketItems.length > 0 ? ticketItems : prev.items,
|
|
661
|
+
email_subject: `${prev.title} - ${brandModel} (${ticketRef})`,
|
|
662
|
+
notes: prev.notes || `Linked to Repair Ticket ${ticketRef}. Device: ${brandModel} (S/N: ${serialImei}).`
|
|
438
663
|
}));
|
|
439
664
|
};
|
|
665
|
+
const handleClearRepairLink = () => {
|
|
666
|
+
setSelectedTicketId("");
|
|
667
|
+
};
|
|
668
|
+
const selectCustomDocPreset = (presetId) => {
|
|
669
|
+
const preset = CUSTOM_DOC_PRESETS.find((p) => p.id === presetId);
|
|
670
|
+
if (!preset) return;
|
|
671
|
+
setCustomDocForm((prev) => {
|
|
672
|
+
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
673
|
+
const updatedFields = {
|
|
674
|
+
...preset.custom_fields || {},
|
|
675
|
+
...selectedTicketId ? {
|
|
676
|
+
"Device Model": ((_a2 = prev.custom_fields) == null ? void 0 : _a2["Device Model"]) || ((_b2 = preset.custom_fields) == null ? void 0 : _b2["Device Model"]),
|
|
677
|
+
"Serial / IMEI": ((_c = prev.custom_fields) == null ? void 0 : _c["Serial / IMEI"]) || ((_d = preset.custom_fields) == null ? void 0 : _d["Serial / IMEI"]),
|
|
678
|
+
"Ticket Reference": ((_e = prev.custom_fields) == null ? void 0 : _e["Ticket Reference"]) || ((_f = preset.custom_fields) == null ? void 0 : _f["Ticket Reference"]),
|
|
679
|
+
"Technician": ((_g = prev.custom_fields) == null ? void 0 : _g["Technician"]) || ((_h = preset.custom_fields) == null ? void 0 : _h["Technician"]),
|
|
680
|
+
"Accessories": ((_i = prev.custom_fields) == null ? void 0 : _i["Accessories"]) || ((_j = preset.custom_fields) == null ? void 0 : _j["Accessories"]),
|
|
681
|
+
"Condition": ((_k = prev.custom_fields) == null ? void 0 : _k["Condition"]) || ((_l = preset.custom_fields) == null ? void 0 : _l["Condition"]),
|
|
682
|
+
"Reported Issue": ((_m = prev.custom_fields) == null ? void 0 : _m["Reported Issue"]) || ((_n = preset.custom_fields) == null ? void 0 : _n["Reported Issue"])
|
|
683
|
+
} : {}
|
|
684
|
+
};
|
|
685
|
+
return {
|
|
686
|
+
...prev,
|
|
687
|
+
preset_id: preset.id,
|
|
688
|
+
title: preset.title,
|
|
689
|
+
email_subject: ((_o = prev.custom_fields) == null ? void 0 : _o["Device Model"]) ? `${preset.title} - ${prev.custom_fields["Device Model"]}` : preset.subject,
|
|
690
|
+
body_text: preset.body_text,
|
|
691
|
+
items: preset.items.map((it) => ({ ...it })),
|
|
692
|
+
custom_fields: updatedFields,
|
|
693
|
+
notes: preset.notes != null ? preset.notes : prev.notes
|
|
694
|
+
};
|
|
695
|
+
});
|
|
696
|
+
};
|
|
440
697
|
const [sendForm, setSendForm] = useState({
|
|
441
698
|
channels: ["email"],
|
|
442
699
|
custom_message: "",
|
|
@@ -498,8 +755,8 @@ const DocumentsPage = () => {
|
|
|
498
755
|
setSettings((prev) => ({ ...prev, [key]: val }));
|
|
499
756
|
};
|
|
500
757
|
const handleCreateManual = async () => {
|
|
501
|
-
var
|
|
502
|
-
if (!((
|
|
758
|
+
var _a2;
|
|
759
|
+
if (!((_a2 = manualForm.customer_name) == null ? void 0 : _a2.trim())) {
|
|
503
760
|
alert("Please provide a Customer Name.");
|
|
504
761
|
return;
|
|
505
762
|
}
|
|
@@ -560,12 +817,12 @@ const DocumentsPage = () => {
|
|
|
560
817
|
}
|
|
561
818
|
};
|
|
562
819
|
const handleCreateCustomDoc = async () => {
|
|
563
|
-
var
|
|
564
|
-
if (!((
|
|
820
|
+
var _a2, _b2;
|
|
821
|
+
if (!((_a2 = customDocForm.title) == null ? void 0 : _a2.trim())) {
|
|
565
822
|
alert("Please provide a Document Title.");
|
|
566
823
|
return;
|
|
567
824
|
}
|
|
568
|
-
if (!((
|
|
825
|
+
if (!((_b2 = customDocForm.customer_name) == null ? void 0 : _b2.trim())) {
|
|
569
826
|
alert("Please provide a Customer / Recipient Name.");
|
|
570
827
|
return;
|
|
571
828
|
}
|
|
@@ -584,6 +841,8 @@ const DocumentsPage = () => {
|
|
|
584
841
|
phone: customDocForm.customer_phone
|
|
585
842
|
},
|
|
586
843
|
body_text: customDocForm.body_text,
|
|
844
|
+
notes: customDocForm.notes,
|
|
845
|
+
custom_fields: customDocForm.custom_fields,
|
|
587
846
|
items: customDocForm.items.filter((it) => it.title.trim() !== ""),
|
|
588
847
|
currency_code: customDocForm.currency_code,
|
|
589
848
|
send_email_now: customDocForm.send_email_now,
|
|
@@ -741,7 +1000,7 @@ const DocumentsPage = () => {
|
|
|
741
1000
|
}
|
|
742
1001
|
};
|
|
743
1002
|
const handleDeleteRepairDocuments = async (repairTicketId) => {
|
|
744
|
-
var
|
|
1003
|
+
var _a2, _b2, _c;
|
|
745
1004
|
if (!confirm(
|
|
746
1005
|
"Are you sure you want to delete ALL generated documents for this repair ticket (including in Zoho Books)? This will allow documents to be cleanly regenerated with updated ticket information."
|
|
747
1006
|
)) {
|
|
@@ -754,7 +1013,7 @@ const DocumentsPage = () => {
|
|
|
754
1013
|
});
|
|
755
1014
|
const data = await res.json();
|
|
756
1015
|
if (res.ok && data.success) {
|
|
757
|
-
const zohoCount = (((
|
|
1016
|
+
const zohoCount = (((_a2 = data.deleted_zoho_invoices) == null ? void 0 : _a2.length) || 0) + (((_b2 = data.deleted_zoho_estimates) == null ? void 0 : _b2.length) || 0) + (((_c = data.deleted_zoho_payments) == null ? void 0 : _c.length) || 0);
|
|
758
1017
|
alert(`Successfully deleted ${data.deleted_records || 0} document record(s)${zohoCount > 0 ? ` and ${zohoCount} Zoho document(s)` : ""}!`);
|
|
759
1018
|
loadDocuments();
|
|
760
1019
|
} else {
|
|
@@ -765,19 +1024,19 @@ const DocumentsPage = () => {
|
|
|
765
1024
|
}
|
|
766
1025
|
};
|
|
767
1026
|
const filteredDocs = documents.filter((doc) => {
|
|
768
|
-
var
|
|
1027
|
+
var _a2, _b2, _c, _d;
|
|
769
1028
|
if (typeFilter !== "all" && doc.type !== typeFilter) return false;
|
|
770
1029
|
if (sourceFilter !== "all" && doc.source !== sourceFilter) return false;
|
|
771
1030
|
if (searchTerm) {
|
|
772
1031
|
const q = searchTerm.toLowerCase();
|
|
773
|
-
return ((
|
|
1032
|
+
return ((_a2 = doc.display_id) == null ? void 0 : _a2.toLowerCase().includes(q)) || ((_b2 = doc.customer_name) == null ? void 0 : _b2.toLowerCase().includes(q)) || ((_c = doc.customer_email) == null ? void 0 : _c.toLowerCase().includes(q)) || ((_d = doc.title) == null ? void 0 : _d.toLowerCase().includes(q));
|
|
774
1033
|
}
|
|
775
1034
|
return true;
|
|
776
1035
|
});
|
|
777
1036
|
const documentGroups = useMemo(() => {
|
|
778
1037
|
const allGroups = groupDocuments(documents);
|
|
779
1038
|
return allGroups.filter((group) => {
|
|
780
|
-
var
|
|
1039
|
+
var _a2, _b2, _c, _d;
|
|
781
1040
|
if (sourceFilter !== "all") {
|
|
782
1041
|
const matchesSource = group.documents.some((d) => d.source === sourceFilter);
|
|
783
1042
|
if (!matchesSource) return false;
|
|
@@ -788,10 +1047,10 @@ const DocumentsPage = () => {
|
|
|
788
1047
|
}
|
|
789
1048
|
if (searchTerm) {
|
|
790
1049
|
const q = searchTerm.toLowerCase();
|
|
791
|
-
const matchesSearch = ((
|
|
1050
|
+
const matchesSearch = ((_a2 = group.groupKey) == null ? void 0 : _a2.toLowerCase().includes(q)) || ((_b2 = group.customerName) == null ? void 0 : _b2.toLowerCase().includes(q)) || ((_c = group.customerEmail) == null ? void 0 : _c.toLowerCase().includes(q)) || ((_d = group.customerPhone) == null ? void 0 : _d.toLowerCase().includes(q)) || group.documents.some(
|
|
792
1051
|
(d) => {
|
|
793
|
-
var
|
|
794
|
-
return ((
|
|
1052
|
+
var _a3, _b3, _c2;
|
|
1053
|
+
return ((_a3 = d.display_id) == null ? void 0 : _a3.toLowerCase().includes(q)) || ((_b3 = d.title) == null ? void 0 : _b3.toLowerCase().includes(q)) || ((_c2 = d.customer_name) == null ? void 0 : _c2.toLowerCase().includes(q));
|
|
795
1054
|
}
|
|
796
1055
|
);
|
|
797
1056
|
if (!matchesSearch) return false;
|
|
@@ -3053,6 +3312,74 @@ Your *{document_title} #{display_id}* has been generated.
|
|
|
3053
3312
|
/* @__PURE__ */ jsx(FocusModal.Header, { children: /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Create Custom Document & Send Email" }) }),
|
|
3054
3313
|
/* @__PURE__ */ jsxs(FocusModal.Body, { className: "p-6 flex flex-col gap-6 max-w-2xl mx-auto overflow-y-auto max-h-[80vh]", children: [
|
|
3055
3314
|
/* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle", children: "Create a customized document (e.g. Warranty Certificate, Inspection Report, Service Clearance) and directly dispatch via email." }),
|
|
3315
|
+
/* @__PURE__ */ jsxs("div", { className: "p-3.5 border border-ui-border-base rounded-lg bg-ui-bg-subtle flex flex-col gap-2.5", children: [
|
|
3316
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3317
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3318
|
+
/* @__PURE__ */ jsx(DocumentText, { className: "w-4 h-4 text-ui-fg-interactive" }),
|
|
3319
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold text-ui-fg-base", children: "Link / Load from Repair Ticket (Optional)" })
|
|
3320
|
+
] }),
|
|
3321
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3322
|
+
selectedTicketId && /* @__PURE__ */ jsx(
|
|
3323
|
+
Button,
|
|
3324
|
+
{
|
|
3325
|
+
variant: "secondary",
|
|
3326
|
+
size: "small",
|
|
3327
|
+
className: "text-[11px] h-6 px-2 text-ui-fg-muted hover:text-red-600",
|
|
3328
|
+
onClick: handleClearRepairLink,
|
|
3329
|
+
children: "Clear Ticket Link"
|
|
3330
|
+
}
|
|
3331
|
+
),
|
|
3332
|
+
/* @__PURE__ */ jsxs(
|
|
3333
|
+
Button,
|
|
3334
|
+
{
|
|
3335
|
+
variant: "secondary",
|
|
3336
|
+
size: "small",
|
|
3337
|
+
className: "text-[11px] h-6 px-2",
|
|
3338
|
+
onClick: loadRepairTickets,
|
|
3339
|
+
isLoading: loadingRepairs,
|
|
3340
|
+
children: [
|
|
3341
|
+
/* @__PURE__ */ jsx(ArrowPath, { className: "w-3 h-3 mr-1" }),
|
|
3342
|
+
" Refresh Tickets"
|
|
3343
|
+
]
|
|
3344
|
+
}
|
|
3345
|
+
)
|
|
3346
|
+
] })
|
|
3347
|
+
] }),
|
|
3348
|
+
/* @__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." }),
|
|
3349
|
+
/* @__PURE__ */ jsxs(
|
|
3350
|
+
Select,
|
|
3351
|
+
{
|
|
3352
|
+
value: selectedTicketId || void 0,
|
|
3353
|
+
onValueChange: handleSelectRepairTicket,
|
|
3354
|
+
children: [
|
|
3355
|
+
/* @__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 --" }) }),
|
|
3356
|
+
/* @__PURE__ */ jsx(Select.Content, { children: availableTickets.map((t) => {
|
|
3357
|
+
var _a2, _b2, _c, _d, _e;
|
|
3358
|
+
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";
|
|
3359
|
+
const cleanNum = String(t.ticket_number || t.id).replace(/^(RT-|JOB-)+/i, "");
|
|
3360
|
+
const displayRef = `RT-${cleanNum}`;
|
|
3361
|
+
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";
|
|
3362
|
+
return /* @__PURE__ */ jsxs(Select.Item, { value: t.id, children: [
|
|
3363
|
+
displayRef,
|
|
3364
|
+
" — ",
|
|
3365
|
+
custName,
|
|
3366
|
+
" (",
|
|
3367
|
+
brandModel,
|
|
3368
|
+
")"
|
|
3369
|
+
] }, t.id);
|
|
3370
|
+
}) })
|
|
3371
|
+
]
|
|
3372
|
+
}
|
|
3373
|
+
),
|
|
3374
|
+
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: [
|
|
3375
|
+
/* @__PURE__ */ jsx("span", { children: "✓ Loaded details from ticket" }),
|
|
3376
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold underline", children: ((_a = customDocForm.custom_fields) == null ? void 0 : _a["Ticket Reference"]) || selectedTicketId }),
|
|
3377
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
3378
|
+
"• ",
|
|
3379
|
+
(_b = customDocForm.custom_fields) == null ? void 0 : _b["Device Model"]
|
|
3380
|
+
] })
|
|
3381
|
+
] })
|
|
3382
|
+
] }),
|
|
3056
3383
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3057
3384
|
/* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold", children: "Select a Document Preset (Optional)" }),
|
|
3058
3385
|
/* @__PURE__ */ jsx(Text, { className: "text-[11px] text-ui-fg-subtle", children: "Pick a pre-configured template below to automatically populate title, certificate text, and itemized points." }),
|
|
@@ -3314,6 +3641,105 @@ Your *{document_title} #{display_id}* has been generated.
|
|
|
3314
3641
|
] })
|
|
3315
3642
|
] })
|
|
3316
3643
|
] }),
|
|
3644
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-3.5 border border-ui-border-base rounded-lg bg-ui-bg-subtle", children: [
|
|
3645
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
|
|
3646
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold text-ui-fg-base", children: "Equipment & Record Specifications (Custom Fields)" }),
|
|
3647
|
+
/* @__PURE__ */ jsx(Text, { className: "text-[11px] text-ui-fg-subtle", children: "Key-value metadata printed prominently in the client & equipment specifications summary box." })
|
|
3648
|
+
] }) }),
|
|
3649
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 mt-1", children: [
|
|
3650
|
+
Object.entries(customDocForm.custom_fields || {}).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-2 items-center bg-ui-bg-base p-2 rounded border border-ui-border-base", children: [
|
|
3651
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-4", children: /* @__PURE__ */ jsx(Label, { className: "text-[11px] text-ui-fg-subtle mb-0.5", children: key }) }),
|
|
3652
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-7", children: /* @__PURE__ */ jsx(
|
|
3653
|
+
Input,
|
|
3654
|
+
{
|
|
3655
|
+
size: "small",
|
|
3656
|
+
value,
|
|
3657
|
+
onChange: (e) => {
|
|
3658
|
+
setCustomDocForm({
|
|
3659
|
+
...customDocForm,
|
|
3660
|
+
custom_fields: {
|
|
3661
|
+
...customDocForm.custom_fields || {},
|
|
3662
|
+
[key]: e.target.value
|
|
3663
|
+
}
|
|
3664
|
+
});
|
|
3665
|
+
},
|
|
3666
|
+
placeholder: `Enter ${key}...`
|
|
3667
|
+
}
|
|
3668
|
+
) }),
|
|
3669
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-1 flex justify-end", children: /* @__PURE__ */ jsx(
|
|
3670
|
+
"button",
|
|
3671
|
+
{
|
|
3672
|
+
type: "button",
|
|
3673
|
+
onClick: () => {
|
|
3674
|
+
const updated = { ...customDocForm.custom_fields || {} };
|
|
3675
|
+
delete updated[key];
|
|
3676
|
+
setCustomDocForm({ ...customDocForm, custom_fields: updated });
|
|
3677
|
+
},
|
|
3678
|
+
className: "text-ui-fg-muted hover:text-red-500 p-1 rounded hover:bg-ui-bg-subtle transition-colors cursor-pointer",
|
|
3679
|
+
title: "Delete Field",
|
|
3680
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "w-3.5 h-3.5" })
|
|
3681
|
+
}
|
|
3682
|
+
) })
|
|
3683
|
+
] }, key)),
|
|
3684
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-2 items-center pt-1", children: [
|
|
3685
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-4", children: /* @__PURE__ */ jsx(
|
|
3686
|
+
Input,
|
|
3687
|
+
{
|
|
3688
|
+
size: "small",
|
|
3689
|
+
placeholder: "New Field Name (e.g. Battery Health)",
|
|
3690
|
+
value: newCustomFieldKey,
|
|
3691
|
+
onChange: (e) => setNewCustomFieldKey(e.target.value)
|
|
3692
|
+
}
|
|
3693
|
+
) }),
|
|
3694
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-6", children: /* @__PURE__ */ jsx(
|
|
3695
|
+
Input,
|
|
3696
|
+
{
|
|
3697
|
+
size: "small",
|
|
3698
|
+
placeholder: "Field Value (e.g. 98% Peak Performance)",
|
|
3699
|
+
value: newCustomFieldValue,
|
|
3700
|
+
onChange: (e) => setNewCustomFieldValue(e.target.value)
|
|
3701
|
+
}
|
|
3702
|
+
) }),
|
|
3703
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxs(
|
|
3704
|
+
Button,
|
|
3705
|
+
{
|
|
3706
|
+
type: "button",
|
|
3707
|
+
variant: "secondary",
|
|
3708
|
+
size: "small",
|
|
3709
|
+
className: "w-full text-xs",
|
|
3710
|
+
onClick: () => {
|
|
3711
|
+
if (!newCustomFieldKey.trim()) return;
|
|
3712
|
+
setCustomDocForm({
|
|
3713
|
+
...customDocForm,
|
|
3714
|
+
custom_fields: {
|
|
3715
|
+
...customDocForm.custom_fields || {},
|
|
3716
|
+
[newCustomFieldKey.trim()]: newCustomFieldValue.trim()
|
|
3717
|
+
}
|
|
3718
|
+
});
|
|
3719
|
+
setNewCustomFieldKey("");
|
|
3720
|
+
setNewCustomFieldValue("");
|
|
3721
|
+
},
|
|
3722
|
+
children: [
|
|
3723
|
+
/* @__PURE__ */ jsx(Plus, { className: "w-3 h-3 mr-1" }),
|
|
3724
|
+
" Add Field"
|
|
3725
|
+
]
|
|
3726
|
+
}
|
|
3727
|
+
) })
|
|
3728
|
+
] })
|
|
3729
|
+
] })
|
|
3730
|
+
] }),
|
|
3731
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3732
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold", children: "Terms & Conditions / Special Notes (Printed at bottom)" }),
|
|
3733
|
+
/* @__PURE__ */ jsx(
|
|
3734
|
+
Textarea,
|
|
3735
|
+
{
|
|
3736
|
+
value: customDocForm.notes || "",
|
|
3737
|
+
onChange: (e) => setCustomDocForm({ ...customDocForm, notes: e.target.value }),
|
|
3738
|
+
placeholder: "Official warranty terms, legal compliance notes, or inspection disclaimers...",
|
|
3739
|
+
rows: 3
|
|
3740
|
+
}
|
|
3741
|
+
)
|
|
3742
|
+
] }),
|
|
3317
3743
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3318
3744
|
/* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold", children: "Email Subject" }),
|
|
3319
3745
|
/* @__PURE__ */ jsx(
|