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