@apptimate/ui 5.9.0 → 6.0.0
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.
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
|
|
2
|
+
import { QRCodeSVG } from "qrcode.react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import Barcode from "react-barcode";
|
|
5
|
+
|
|
6
|
+
export function replaceTokens(text: string, entityData: any): string {
|
|
7
|
+
if (!text || typeof text !== "string") return text;
|
|
8
|
+
const data = entityData || {};
|
|
9
|
+
let result = text.replace(/\{\{\s*([\w.]+)\s*\}\}(\s*g(?!\w))?/g, (_, path, trailingG: string | undefined) => {
|
|
10
|
+
const keys = path.trim().split(".");
|
|
11
|
+
let val = data;
|
|
12
|
+
for (const k of keys) {
|
|
13
|
+
if (val === undefined || val === null) break;
|
|
14
|
+
val = val[k];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// If undefined, let's try row context fallback
|
|
18
|
+
if ((val === undefined || val === null) && keys.length > 1) {
|
|
19
|
+
// If user provided a nested key but it's directly on data
|
|
20
|
+
if (data[path.trim()] !== undefined) {
|
|
21
|
+
val = data[path.trim()];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const fmtCurrency = (v: any) => (v && !isNaN(Number(v)) && Number(v) > 0) ? `${data.currency_code || ""} ${Number(v).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : "—";
|
|
26
|
+
|
|
27
|
+
// Helper to compute payment info (shared between payment_breakdown and credit_display)
|
|
28
|
+
const computePaymentInfo = () => {
|
|
29
|
+
const payments = data.payments || data.invoice?.payments || [];
|
|
30
|
+
const grandTotal = Number(data.grand_total || data.total_estimated_cost || 0);
|
|
31
|
+
let amountPaid = 0;
|
|
32
|
+
let paidLines: string[] = [];
|
|
33
|
+
|
|
34
|
+
if (payments.length === 0) {
|
|
35
|
+
amountPaid = Number(data.amount_paid || data.total_advance || 0);
|
|
36
|
+
if (data.payment_method && String(data.payment_method).toLowerCase() !== "credit" && amountPaid > 0) {
|
|
37
|
+
const methodStr = String(data.payment_method).toUpperCase();
|
|
38
|
+
paidLines.push(`Paid (${methodStr}) - ${fmtCurrency(amountPaid)}`);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
payments.forEach((p: any) => {
|
|
42
|
+
const methodStr = String(p.mode_code || p.payment_mode?.name || p.payment_method || "PAYMENT").toUpperCase();
|
|
43
|
+
if (methodStr === "CREDIT") return; // skip credit from paid lines
|
|
44
|
+
const amt = Number(p.amount ?? p.payment_amount ?? 0);
|
|
45
|
+
if (amt > 0) {
|
|
46
|
+
amountPaid += amt;
|
|
47
|
+
paidLines.push(`Paid (${methodStr}) - ${fmtCurrency(amt)}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Calculate the remaining balance strictly based on what is being displayed
|
|
53
|
+
// as paid to prevent duplicate credit/cash balance display
|
|
54
|
+
const balance = Math.max(0, grandTotal - amountPaid);
|
|
55
|
+
|
|
56
|
+
const hasCredit = balance > 0.01 || (paidLines.length === 0 && grandTotal > 0 && amountPaid === 0);
|
|
57
|
+
const creditAmount = balance > 0.01 ? balance : (paidLines.length === 0 && grandTotal > 0 && amountPaid === 0 ? grandTotal : 0);
|
|
58
|
+
|
|
59
|
+
return { paidLines, hasCredit, creditAmount, grandTotal };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Fallback logic for payment_breakdown (only paid methods, no credit)
|
|
63
|
+
if ((val === undefined || val === null) && path.trim() === "payment_breakdown") {
|
|
64
|
+
const { paidLines, hasCredit, creditAmount, grandTotal } = computePaymentInfo();
|
|
65
|
+
|
|
66
|
+
if (paidLines.length > 0) {
|
|
67
|
+
val = paidLines.join("\n");
|
|
68
|
+
} else if (hasCredit) {
|
|
69
|
+
val = "Credit - " + fmtCurrency(creditAmount);
|
|
70
|
+
} else {
|
|
71
|
+
val = "Credit - " + fmtCurrency(grandTotal);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback logic for refund_breakdown (shows refund methods and credit)
|
|
76
|
+
if ((val === undefined || val === null) && path.trim() === "refund_breakdown") {
|
|
77
|
+
const payments = data.payments || data.invoice?.payments || [];
|
|
78
|
+
const totalRefund = Number(data.refund_amount || data.grand_total || 0);
|
|
79
|
+
let amountRefunded = 0;
|
|
80
|
+
let refundLines: string[] = [];
|
|
81
|
+
|
|
82
|
+
if (payments.length === 0) {
|
|
83
|
+
amountRefunded = totalRefund;
|
|
84
|
+
if (data.refund_method && String(data.refund_method).toLowerCase() !== "credit" && amountRefunded > 0) {
|
|
85
|
+
const methodStr = String(data.refund_method).charAt(0).toUpperCase() + String(data.refund_method).slice(1);
|
|
86
|
+
refundLines.push(`Refund (${methodStr}) ${fmtCurrency(amountRefunded)}`);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
payments.forEach((p: any) => {
|
|
90
|
+
const methodStr = String(p.mode_code || p.payment_mode?.name || p.payment_method || "PAYMENT");
|
|
91
|
+
const formattedMethod = methodStr.charAt(0).toUpperCase() + methodStr.slice(1).toLowerCase();
|
|
92
|
+
if (formattedMethod.toUpperCase() === "CREDIT") return; // skip credit from paid lines
|
|
93
|
+
const amt = Number(p.amount ?? p.payment_amount ?? 0);
|
|
94
|
+
if (amt > 0) {
|
|
95
|
+
amountRefunded += amt;
|
|
96
|
+
refundLines.push(`Refund (${formattedMethod}) ${fmtCurrency(amt)}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const balance = Math.max(0, totalRefund - amountRefunded);
|
|
102
|
+
const hasCredit = balance > 0.01 || (refundLines.length === 0 && totalRefund > 0 && amountRefunded === 0);
|
|
103
|
+
const creditAmount = balance > 0.01 ? balance : (refundLines.length === 0 && totalRefund > 0 && amountRefunded === 0 ? totalRefund : 0);
|
|
104
|
+
|
|
105
|
+
if (refundLines.length > 0) {
|
|
106
|
+
val = refundLines.join("\n");
|
|
107
|
+
if (hasCredit) val += `\nRefund (Credit) ${fmtCurrency(creditAmount)}`;
|
|
108
|
+
} else if (hasCredit) {
|
|
109
|
+
val = "Refund (Credit) " + fmtCurrency(creditAmount);
|
|
110
|
+
} else {
|
|
111
|
+
val = "Refund (Credit) " + fmtCurrency(totalRefund);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Fallback logic for payment_breakdown_labels and _amounts
|
|
116
|
+
if ((val === undefined || val === null) && path.trim() === "payment_breakdown_labels") {
|
|
117
|
+
const { paidLines, hasCredit } = computePaymentInfo();
|
|
118
|
+
if (paidLines.length > 0) {
|
|
119
|
+
val = paidLines.map(l => l.split(' - ')[0]).join("\n");
|
|
120
|
+
} else if (hasCredit) {
|
|
121
|
+
val = "Credit";
|
|
122
|
+
} else {
|
|
123
|
+
val = "Credit";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if ((val === undefined || val === null) && path.trim() === "payment_breakdown_amounts") {
|
|
127
|
+
const { paidLines, hasCredit, creditAmount, grandTotal } = computePaymentInfo();
|
|
128
|
+
if (paidLines.length > 0) {
|
|
129
|
+
val = paidLines.map(l => l.split(' - ')[1]).join("\n");
|
|
130
|
+
} else if (hasCredit) {
|
|
131
|
+
val = fmtCurrency(creditAmount);
|
|
132
|
+
} else {
|
|
133
|
+
val = fmtCurrency(grandTotal);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Fallback logic for credit_display (shows only if there is credit/balance due)
|
|
138
|
+
if ((val === undefined || val === null) && path.trim() === "credit_display") {
|
|
139
|
+
const { paidLines, hasCredit, creditAmount } = computePaymentInfo();
|
|
140
|
+
if (hasCredit && paidLines.length > 0) {
|
|
141
|
+
val = `Credit - ${fmtCurrency(creditAmount)}`;
|
|
142
|
+
} else {
|
|
143
|
+
val = ""; // no credit to show, or fully on credit (already shown in payment_breakdown)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Fallback logic for credit_display_labels and _amounts
|
|
148
|
+
if ((val === undefined || val === null) && path.trim() === "credit_display_labels") {
|
|
149
|
+
const { paidLines, hasCredit } = computePaymentInfo();
|
|
150
|
+
if (hasCredit && paidLines.length > 0) {
|
|
151
|
+
val = "Credit";
|
|
152
|
+
} else {
|
|
153
|
+
val = "";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if ((val === undefined || val === null) && path.trim() === "credit_display_amounts") {
|
|
157
|
+
const { paidLines, hasCredit, creditAmount } = computePaymentInfo();
|
|
158
|
+
if (hasCredit && paidLines.length > 0) {
|
|
159
|
+
val = fmtCurrency(creditAmount);
|
|
160
|
+
} else {
|
|
161
|
+
val = "";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Fallback logic for deduction_display (shows only if there is a deduction)
|
|
166
|
+
if ((val === undefined || val === null) && path.trim() === "deduction_display") {
|
|
167
|
+
const deduction = Number(data.deduction_amount || 0);
|
|
168
|
+
if (deduction > 0) {
|
|
169
|
+
val = `Deduction - ${fmtCurrency(deduction)}`;
|
|
170
|
+
} else {
|
|
171
|
+
val = "";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if ((val === undefined || val === null) && path.trim() === "total") val = data.grand_total;
|
|
176
|
+
if ((val === undefined || val === null) && path.trim() === "so_number") val = data.sale_number;
|
|
177
|
+
if ((val === undefined || val === null) && path.trim() === "so_date") val = new Date(data.created_at || data.sale_date).toLocaleDateString();
|
|
178
|
+
if ((val === undefined || val === null) && path.trim() === "expected_date") val = data.expected_date ? new Date(data.expected_date).toLocaleDateString() : (data.created_at ? new Date(data.created_at).toLocaleDateString() : "—");
|
|
179
|
+
if ((val === undefined || val === null) && path.trim() === "delivery_address") val = data.customer?.address || "N/A";
|
|
180
|
+
if ((val === undefined || val === null) && path.trim() === "po_number") val = data.po_number || "—";
|
|
181
|
+
if ((val === undefined || val === null) && path.trim() === "order_number") val = data.order_number || "—";
|
|
182
|
+
if ((val === undefined || val === null) && path.trim() === "sale_number") val = data.sale_number || "—";
|
|
183
|
+
if ((val === undefined || val === null) && path.trim() === "sale_date") val = data.sale_date ? new Date(data.sale_date).toLocaleDateString() : (data.created_at ? new Date(data.created_at).toLocaleDateString() : "—");
|
|
184
|
+
if ((val === undefined || val === null) && path.trim() === "sale_time") val = data.sale_time || (data.created_at ? new Date(data.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : "—");
|
|
185
|
+
if ((val === undefined || val === null) && path.trim() === "purchase_number") val = data.purchase_number || "—";
|
|
186
|
+
if ((val === undefined || val === null) && path.trim() === "total_estimated_cost") val = fmtCurrency(data.total_estimated_cost);
|
|
187
|
+
if ((val === undefined || val === null) && path.trim() === "total_advance") val = fmtCurrency(data.total_advance);
|
|
188
|
+
if ((val === undefined || val === null) && path.trim() === "balance_due") val = fmtCurrency(data.balance_due);
|
|
189
|
+
if ((val === undefined || val === null) && path.trim() === "discount_amount") val = fmtCurrency(data.discount_total || data.discount_amount || 0);
|
|
190
|
+
if ((val === undefined || val === null) && path.trim() === "tax_amount") val = fmtCurrency(data.tax_total || data.tax_amount || 0);
|
|
191
|
+
if ((val === undefined || val === null) && path.trim() === "subtotal") val = fmtCurrency(data.subtotal || data.total_estimated_cost || 0);
|
|
192
|
+
if ((val === undefined || val === null) && path.trim() === "order_date") val = data.order_date ? new Date(data.order_date).toLocaleDateString() : (data.created_at ? new Date(data.created_at).toLocaleDateString() : "—");
|
|
193
|
+
if ((val === undefined || val === null) && path.trim() === "customer_expected_date") val = data.customer_expected_date ? new Date(data.customer_expected_date).toLocaleDateString() : "—";
|
|
194
|
+
if ((val === undefined || val === null) && path.trim() === "occasion") val = data.occasion || "—";
|
|
195
|
+
if ((val === undefined || val === null) && path.trim() === "occasion_date") val = data.occasion_date ? new Date(data.occasion_date).toLocaleDateString() : "—";
|
|
196
|
+
if ((val === undefined || val === null) && path.trim() === "design_notes") val = data.design_notes || "None";
|
|
197
|
+
if ((val === undefined || val === null) && path.trim() === "created_at") val = data.created_at ? new Date(data.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : "—";
|
|
198
|
+
if ((val === undefined || val === null) && path.trim() === "updated_at") val = data.updated_at ? new Date(data.updated_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : "—";
|
|
199
|
+
|
|
200
|
+
// Fallback logic for barcode tokens
|
|
201
|
+
if ((val === undefined || val === null) && path.trim() === "item_name") val = data.name || data.item_name;
|
|
202
|
+
if ((val === undefined || val === null) && path.trim() === "item_sku") val = data.sku || data.code || data.item_sku;
|
|
203
|
+
if ((val === undefined || val === null) && path.trim() === "barcode") val = data.barcode || data.code || data.sku;
|
|
204
|
+
if ((val === undefined || val === null) && path.trim() === "qr_code") val = data.qr_code || data.barcode || data.code || data.sku;
|
|
205
|
+
if ((val === undefined || val === null) && path.trim() === "category") val = data.category?.name || data.category;
|
|
206
|
+
if ((val === undefined || val === null) && path.trim() === "weight") val = data.weight || data.gross_weight || data.net_weight;
|
|
207
|
+
if ((val === undefined || val === null) && path.trim() === "karat") val = data.purity?.karat || data.karat;
|
|
208
|
+
|
|
209
|
+
// Fallbacks for inventory-specific tokens
|
|
210
|
+
if ((val === undefined || val === null) && path.trim() === "inventory_items.name") val = data.name || data.item_name;
|
|
211
|
+
if ((val === undefined || val === null) && (path.trim() === "inventory_items.sku" || path.trim() === "sku")) val = data.sku || data.code || data.item_sku;
|
|
212
|
+
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.variant_name") val = data.variants?.[0]?.variant_name || data.name;
|
|
213
|
+
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.sku") val = data.variants?.[0]?.sku || data.sku || data.code;
|
|
214
|
+
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.barcode") val = data.variants?.[0]?.barcode || data.barcode || data.sku || data.code;
|
|
215
|
+
if ((val === undefined || val === null) && (path.trim() === "inventory_item_variants.sale_price" || path.trim() === "sale_price" || path.trim() === "price")) val = data.sale_price || data.price || data.variants?.[0]?.sale_price || 0;
|
|
216
|
+
if ((val === undefined || val === null) && (path.trim() === "inventory_item_variants.cost_price" || path.trim() === "cost_price")) val = data.cost_price || data.variants?.[0]?.cost_price || 0;
|
|
217
|
+
if ((val === undefined || val === null) && path.trim() === "inventory_batches.batch_number") val = data.batch_number || data.batch?.batch_number || "";
|
|
218
|
+
if ((val === undefined || val === null) && path.trim() === "inventory_batches.selling_price") val = data.price || data.selling_price || data.batch?.selling_price || data.sale_price || 0;
|
|
219
|
+
|
|
220
|
+
// Table Row Fallbacks for tokens that start with "items." (e.g. items.product.name)
|
|
221
|
+
if ((val === undefined || val === null) && path.trim().startsWith("items.") && !Array.isArray(data.items)) {
|
|
222
|
+
const p = path.trim().toLowerCase();
|
|
223
|
+
// General fallback: remove "items." and resolve the rest on data
|
|
224
|
+
const subPath = path.trim().substring(6);
|
|
225
|
+
const subKeys = subPath.split(".");
|
|
226
|
+
let subVal = data;
|
|
227
|
+
for (const sk of subKeys) {
|
|
228
|
+
if (subVal === undefined || subVal === null) break;
|
|
229
|
+
subVal = subVal[sk];
|
|
230
|
+
}
|
|
231
|
+
if (subVal !== undefined && subVal !== null) {
|
|
232
|
+
val = subVal;
|
|
233
|
+
} else {
|
|
234
|
+
if (p.includes("name") || p.includes("product") || p.includes("description")) val = data.description || data.item?.name || data.item_name;
|
|
235
|
+
if (p.includes("quantity") || p.includes("qty")) val = data.qty || data.quantity;
|
|
236
|
+
if (p.includes("total") || p.includes("amount") || p.includes("price") || p.includes("cost")) val = data.amount || data.subtotal || data.line_total;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
let orgData: any = {};
|
|
240
|
+
try {
|
|
241
|
+
if (typeof window !== 'undefined') {
|
|
242
|
+
const stored = localStorage.getItem('selected_organization');
|
|
243
|
+
if (stored) orgData = JSON.parse(stored) || {};
|
|
244
|
+
}
|
|
245
|
+
} catch { }
|
|
246
|
+
|
|
247
|
+
if ((val === undefined || val === null) && path.trim() === "company_name") val = data.organization?.name || orgData.name || "";
|
|
248
|
+
if ((val === undefined || val === null) && path.trim() === "organization.address") val = data.organization?.location || orgData.location || "";
|
|
249
|
+
if ((val === undefined || val === null) && path.trim() === "organization.phone") val = data.organization?.primary_contact_number || orgData.primary_contact_number || "";
|
|
250
|
+
if ((val === undefined || val === null) && path.trim() === "organization.email") val = data.organization?.email || orgData.email || "";
|
|
251
|
+
if ((val === undefined || val === null) && (path.trim() === "organization.logo" || path.trim() === "organization.logo_url")) val = data.organization?.logo_url || orgData.logo_url || "";
|
|
252
|
+
if ((val === undefined || val === null) && path.trim() === "warehouse.address") val = data.warehouse?.location || "";
|
|
253
|
+
if ((val === undefined || val === null) && path.trim() === "warehouse.phone") val = data.warehouse?.primary_contact_number || "";
|
|
254
|
+
if ((val === undefined || val === null) && path.trim().startsWith("organization.")) {
|
|
255
|
+
const field = path.trim().split(".")[1];
|
|
256
|
+
if (field) {
|
|
257
|
+
val = data.organization?.[field] ?? orgData[field] ?? "";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Format dates nicely
|
|
262
|
+
if (val !== undefined && val !== null && (path.includes("date") || path.includes("_at"))) {
|
|
263
|
+
if (typeof val === "string" && !isNaN(Date.parse(val)) && !path.includes("updated_at") && !path.includes("created_at")) {
|
|
264
|
+
val = new Date(val).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
265
|
+
} else if (typeof val === "string" && !isNaN(Date.parse(val))) {
|
|
266
|
+
val = new Date(val).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Format specific currency properties gracefully if they are numbers
|
|
271
|
+
if (val !== undefined && val !== null && (path.includes("amount") || path.includes("total") || path.includes("cost") || path.includes("price"))) {
|
|
272
|
+
if (!isNaN(Number(val)) && Number(val) > 0) {
|
|
273
|
+
val = `${data.currency_code ? data.currency_code + ' ' : ''}${Number(val).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Hide zero weights
|
|
278
|
+
if (path.includes("weight") && val !== undefined && val !== null) {
|
|
279
|
+
const strVal = String(val).trim();
|
|
280
|
+
if (strVal === "0" || strVal === "0.00" || strVal === "0.000" || strVal === "") {
|
|
281
|
+
val = "";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const finalVal = val !== undefined && val !== null && String(val).trim() !== "" ? String(val) : "";
|
|
286
|
+
return finalVal ? finalVal + (trailingG || "") : "";
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Cleanup dangling or repeated separators caused by empty token replacements
|
|
290
|
+
result = result.replace(/(?:\s*-\s*){2,}/g, ' - ').replace(/^(?:\s*-\s*)+/, '').replace(/(?:\s*-\s*)+$/, '');
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function TemplateRenderer({ template, lines, entityData, noPadding }: { template: any; lines: any[]; entityData: any; noPadding?: boolean }) {
|
|
295
|
+
const defaultFont = template.default_font_family || 'Courier New';
|
|
296
|
+
const defaultColor = template.default_font_color || "#000000";
|
|
297
|
+
const defaultFontSize = template.content_config?.default_font_size || 12;
|
|
298
|
+
const isA4 = (template.width_mm || template.page_width_mm || 210) > 100;
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<div style={{ padding: noPadding ? 0 : (template.content_config?.margins ? `${template.content_config.margins.top ?? template.margin_mm ?? 1}mm ${template.content_config.margins.right ?? template.margin_mm ?? 1}mm ${template.content_config.margins.bottom ?? template.margin_mm ?? 1}mm ${template.content_config.margins.left ?? template.margin_mm ?? 1}mm` : `${template.margin_mm !== undefined && template.margin_mm !== null ? template.margin_mm : 1}mm`), backgroundColor: "#fff", color: defaultColor, fontFamily: defaultFont, fontSize: `${defaultFontSize}px` }}>
|
|
302
|
+
{lines.map((line, idx) => (
|
|
303
|
+
<LineRenderer key={idx} line={line} defaultFont={defaultFont} defaultColor={defaultColor} defaultFontSize={defaultFontSize} entityData={entityData} isA4={isA4} />
|
|
304
|
+
))}
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entityData, isA4 }: { line: any; defaultFont: string; defaultColor: string; defaultFontSize: number; entityData: any; isA4?: boolean }) {
|
|
310
|
+
let showIfKey = null;
|
|
311
|
+
if (line.extra_config) {
|
|
312
|
+
try {
|
|
313
|
+
const ec = typeof line.extra_config === 'string' ? JSON.parse(line.extra_config) : line.extra_config;
|
|
314
|
+
if (ec.show_if) showIfKey = ec.show_if;
|
|
315
|
+
} catch (e) { }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (showIfKey) {
|
|
319
|
+
const key = showIfKey.replace(/[{}]/g, '').trim();
|
|
320
|
+
let val = entityData;
|
|
321
|
+
for (const k of key.split('.')) {
|
|
322
|
+
if (val == null) break;
|
|
323
|
+
val = val[k];
|
|
324
|
+
}
|
|
325
|
+
const num = Number(val);
|
|
326
|
+
if (!val || val === "0" || val === 0 || val === "0.00" || val === "—" || (!isNaN(num) && num <= 0)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const baseStyle: React.CSSProperties = {
|
|
332
|
+
fontFamily: `${line.font_family || defaultFont}, sans-serif`,
|
|
333
|
+
fontSize: `${Number(line.font_size) || defaultFontSize}px`,
|
|
334
|
+
lineHeight: 1.15,
|
|
335
|
+
fontWeight: line.font_weight === "bold" ? 700 : line.font_weight === "light" ? 300 : (isA4 === false ? 600 : 400),
|
|
336
|
+
color: line.font_color || defaultColor,
|
|
337
|
+
textAlign: (line.text_align || "left") as any,
|
|
338
|
+
textTransform: (line.text_transform || "none") as any,
|
|
339
|
+
fontStyle: line.is_italic ? "italic" : "normal",
|
|
340
|
+
textDecoration: line.is_underline ? "underline" : "none",
|
|
341
|
+
backgroundColor: line.background_color || "transparent",
|
|
342
|
+
padding: line.padding || "0",
|
|
343
|
+
borderRadius: line.border_radius || "0",
|
|
344
|
+
marginTop: `${line.spacing_before || 0}px`,
|
|
345
|
+
marginBottom: `${line.spacing_after || 0}px`,
|
|
346
|
+
maxWidth: `${line.max_width_percent || 100}%`,
|
|
347
|
+
wordBreak: "break-word" as const,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
if (line.border_style && line.border_style !== "none") {
|
|
351
|
+
const bw = `${line.border_width || 1}px`;
|
|
352
|
+
const bc = line.border_color || "#000000";
|
|
353
|
+
const bs = line.border_style;
|
|
354
|
+
const sides = line.border_sides || "all";
|
|
355
|
+
if (sides === "all") baseStyle.border = `${bw} ${bs} ${bc}`;
|
|
356
|
+
else if (sides === "top-bottom") { baseStyle.borderTop = `${bw} ${bs} ${bc}`; baseStyle.borderBottom = `${bw} ${bs} ${bc}`; }
|
|
357
|
+
else if (sides === "left-right") { baseStyle.borderLeft = `${bw} ${bs} ${bc}`; baseStyle.borderRight = `${bw} ${bs} ${bc}`; }
|
|
358
|
+
else (baseStyle as any)[`border${sides.charAt(0).toUpperCase() + sides.slice(1)}`] = `${bw} ${bs} ${bc}`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Text
|
|
362
|
+
if (line.line_type === "text") {
|
|
363
|
+
return <div style={baseStyle}>{replaceTokens(line.static_text || "", entityData).trim().split("\n").map((t, i) => <div key={i}>{t || <br />}</div>)}</div>;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Token (legacy)
|
|
367
|
+
if (line.line_type === "token") {
|
|
368
|
+
return <div style={baseStyle}>{replaceTokens(`{{${line.token_key}}}`, entityData)}</div>;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Separator
|
|
372
|
+
if (line.line_type === "separator") {
|
|
373
|
+
const isVertical = line.separator_direction === "vertical";
|
|
374
|
+
const style = line.separator_style || "solid";
|
|
375
|
+
const thickness = line.separator_thickness || 1;
|
|
376
|
+
const color = line.separator_color || line.font_color || defaultColor;
|
|
377
|
+
|
|
378
|
+
if (isVertical) return <div style={{ display: "flex", justifyContent: "center", marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px` }}><div style={{ width: 0, height: "24px", borderLeft: `${thickness}px ${style} ${color}` }} /></div>;
|
|
379
|
+
return <div style={{ marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px` }}><hr style={{ border: "none", borderTop: style === "double" ? `${thickness}px double ${color}` : `${thickness}px ${style} ${color}`, margin: 0 }} /></div>;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Spacer
|
|
383
|
+
if (line.line_type === "spacer") {
|
|
384
|
+
return <div style={{ height: `${(line.spacing_before || 4) + (line.spacing_after || 4)}px` }} />;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Image
|
|
388
|
+
if (line.line_type === "image") {
|
|
389
|
+
const rawUrl = (line.extra_config || {}).url || "";
|
|
390
|
+
const url = rawUrl.includes("{{") ? replaceTokens(rawUrl, entityData) : rawUrl;
|
|
391
|
+
const width = (line.extra_config || {}).width_px || 100;
|
|
392
|
+
const align = line.text_align || "center";
|
|
393
|
+
|
|
394
|
+
if (!url) return null; // Don't render a grey box in actual prints if there's no image
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div style={{ marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px`, display: "flex", justifyContent: align === "left" ? "flex-start" : align === "right" ? "flex-end" : "center" }}>
|
|
398
|
+
<img src={url} alt="Logo" style={{ width: `${width}px`, height: "auto", objectFit: "contain" }} />
|
|
399
|
+
</div>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Columns
|
|
404
|
+
if (line.line_type === "columns") {
|
|
405
|
+
const childCols = line.extra_config?.columns || line.columns || [];
|
|
406
|
+
return (
|
|
407
|
+
<div style={{ display: "flex", marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px`, backgroundColor: line.background_color || "transparent", padding: line.padding || "0", borderRadius: line.border_radius || "0" }}>
|
|
408
|
+
{childCols.map((col: any, i: number) => (
|
|
409
|
+
<div key={i} style={{ width: `${col.width_percent || 50}%`, fontFamily: `${col.font_family || line.font_family || defaultFont}, sans-serif`, fontSize: `${col.font_size ? Number(col.font_size) : (line.font_size ? Number(line.font_size) : defaultFontSize)}px`, lineHeight: 1.15, fontWeight: col.font_weight === "bold" ? 700 : col.font_weight === "light" ? 300 : (line.font_weight === "bold" ? 700 : line.font_weight === "light" ? 300 : (isA4 === false ? 600 : 400)), color: col.font_color || line.font_color || defaultColor, textAlign: (col.text_align || col.align || "left") as any, fontStyle: col.is_italic ? "italic" : "normal", textDecoration: col.is_underline ? "underline" : "none", textTransform: (col.text_transform || "none") as any, backgroundColor: col.background_color || "transparent", padding: col.padding || "0", whiteSpace: "pre-wrap" }}>
|
|
410
|
+
{replaceTokens(col.content || col.static_text || "", entityData).split("\n").map((t, ii) => <div key={ii}>{t || <br />}</div>)}
|
|
411
|
+
</div>
|
|
412
|
+
))}
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Table
|
|
418
|
+
if (line.line_type === "table") {
|
|
419
|
+
const ec = line.extra_config || {};
|
|
420
|
+
const cols = ec.columns || [];
|
|
421
|
+
const showHeader = ec.show_header !== false;
|
|
422
|
+
const headerBg = ec.header_background || "#003366";
|
|
423
|
+
const headerFc = ec.header_font_color || "#FFFFFF";
|
|
424
|
+
const rowSep = ec.row_separator || "dotted";
|
|
425
|
+
const stripeBg = ec.row_stripe_color || "#F8FAFC";
|
|
426
|
+
const headerFs = ec.header_font_size ? Number(ec.header_font_size) : (line.font_size ? Number(line.font_size) : defaultFontSize);
|
|
427
|
+
const rowFs = ec.row_font_size ? Number(ec.row_font_size) : (line.font_size ? Number(line.font_size) - 1 : (defaultFontSize - 1 > 0 ? defaultFontSize - 1 : 11));
|
|
428
|
+
const borderColor = ec.border_color || "#E5E7EB";
|
|
429
|
+
const outerBorder = ec.outer_border !== false;
|
|
430
|
+
|
|
431
|
+
// Resolve data source
|
|
432
|
+
const dataSourceRaw = ec.data_source || "{{ line_items }}";
|
|
433
|
+
const dsKey = dataSourceRaw.replace(/[{}]/g, '').trim();
|
|
434
|
+
let rowsData = [];
|
|
435
|
+
if (dsKey === "line_items" || dsKey === "items") {
|
|
436
|
+
rowsData = entityData.items || entityData.lines || [];
|
|
437
|
+
} else {
|
|
438
|
+
let val = entityData;
|
|
439
|
+
for (const k of dsKey.split('.')) {
|
|
440
|
+
if (val == null) break;
|
|
441
|
+
val = val[k];
|
|
442
|
+
}
|
|
443
|
+
rowsData = val || [];
|
|
444
|
+
}
|
|
445
|
+
if (!Array.isArray(rowsData)) rowsData = [];
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<div style={{ marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px`, border: outerBorder ? `1px solid ${borderColor}` : "none", borderRadius: line.border_radius || "0", overflow: "hidden", fontFamily: line.font_family || defaultFont }}>
|
|
449
|
+
{showHeader && (
|
|
450
|
+
<div style={{ display: "flex", flexWrap: "wrap", backgroundColor: headerBg, padding: ec.header_padding || "6px 8px" }}>
|
|
451
|
+
{cols.map((col: any, i: number) => (
|
|
452
|
+
<div key={i} style={{ width: `${col.header_width_percent || col.width_percent}%`, color: headerFc, fontSize: `${headerFs}px`, fontWeight: ec.header_bold !== false ? 700 : (isA4 === false ? 600 : 400), textAlign: col.align || "left", whiteSpace: "pre-wrap" }}>
|
|
453
|
+
{typeof col.label === 'string' ? col.label.split('\n').map((t: string, ti: number) => <div key={ti}>{t || <br />}</div>) : col.label}
|
|
454
|
+
</div>
|
|
455
|
+
))}
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
458
|
+
{rowsData.map((row: any, ri: number) => {
|
|
459
|
+
const curCode = entityData.currency_code ? entityData.currency_code + ' ' : "";
|
|
460
|
+
const fmtCurrency = (val: any) => (val && Number(val) > 0) ? `${curCode}${Number(val).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : "—";
|
|
461
|
+
const fmtNum = (val: any, suffix = "") => (val && Number(val) > 0) ? `${parseFloat(Number(val).toFixed(4))}${suffix}` : "—";
|
|
462
|
+
const fmtQty = (val: any) => (val && Number(val) > 0) ? parseFloat(Number(val).toFixed(4)).toString() : "—";
|
|
463
|
+
|
|
464
|
+
const rowDataCtx = {
|
|
465
|
+
...row,
|
|
466
|
+
index: ri + 1,
|
|
467
|
+
description: row.item?.name || row.item_name || "Item",
|
|
468
|
+
specification: row.item?.code || "",
|
|
469
|
+
|
|
470
|
+
// Rich item details for multi-line support
|
|
471
|
+
item_details: `${row.item_description || row.item_name || row.item?.name || "Item"}\n${row.item?.code ? row.item.code + " " : ""}${String(row.line_type || row.item?.category?.name || "JEWELRY").replace(/_/g, ' ').toUpperCase()}`,
|
|
472
|
+
type: row.receipt_type ? (row.receipt_type === 'bulk' ? 'BULK / LOT' : String(row.receipt_type).replace(/_/g, ' ').toUpperCase()) : (row.item?.item_type ? row.item.item_type.replace('_', ' ').toUpperCase() : (row.item_class || row.item?.item_class ? String(row.item_class || row.item?.item_class).replace('_', ' ').toUpperCase() : (row.item_type || "BULK / LOT").toUpperCase())),
|
|
473
|
+
// New richer fields for GRN matching UI
|
|
474
|
+
material_purity_rich: `${(row.material_type || row.item?.material_type)?.name || "Gold"}\n${row.purity?.purity_percentage || row.purity_percentage || "99.8"}% (${row.purity?.karat || row.purity?.purity_code || "24K"})\nRate: ${fmtCurrency(row.rate_per_gram || row.unit_price)} / g`,
|
|
475
|
+
qty: fmtQty(row.quantity || row.received_quantity || 1),
|
|
476
|
+
gross_wt: fmtNum(row.gross_weight || row.weight_grams || row.metal_weight || row.sale_weight || row.weight, ""),
|
|
477
|
+
weight: fmtNum(row.sale_weight || row.weight || row.gross_weight || row.weight_grams || row.metal_weight, ""),
|
|
478
|
+
material_wt: fmtNum(row.sale_weight || row.metal_weight || row.weight || row.gross_weight || row.weight_grams, ""),
|
|
479
|
+
weight_display: (row.weight_grams || row.net_weight || row.estimated_weight || row.material_weight || row.sale_weight || row.weight) && Number(row.weight_grams || row.net_weight || row.estimated_weight || row.material_weight || row.sale_weight || row.weight) > 0 ? `${parseFloat(Number(row.weight_grams || row.net_weight || row.estimated_weight || row.material_weight || row.sale_weight || row.weight).toFixed(4))}g` : "",
|
|
480
|
+
stone_wt: fmtNum(row.sale_stone_weight || row.stone_weight, ""),
|
|
481
|
+
stone_rich: row.stone_weight && Number(row.stone_weight) > 0 ? `${fmtNum(row.stone_weight, "g")}\n@ ${fmtCurrency(row.stone_cost || row.stone_amount)}` : "—",
|
|
482
|
+
item_value: fmtCurrency(row.item_value || row.subtotal || row.unit_price || row.amount),
|
|
483
|
+
charges_rich: [
|
|
484
|
+
(row.making_charge || row.making_charge_value) && Number(row.making_charge || row.making_charge_value) > 0 ? `MC: ${row.making_charge || row.making_charge_value} (${row.making_charge_type || 'fixed'})` : null,
|
|
485
|
+
(row.wastage || row.wastage_value) && Number(row.wastage || row.wastage_value) > 0 ? `W: ${row.wastage || row.wastage_value} (${row.wastage_type || 'fixed'})` : null,
|
|
486
|
+
(row.discount || row.discount_amount) && Number(row.discount || row.discount_amount) > 0 ? `Disc: -${fmtCurrency(row.discount || row.discount_amount)}` : null,
|
|
487
|
+
(row.tax_rate || row.tax_percentage) && Number(row.tax_rate || row.tax_percentage) > 0 ? `Tax: ${row.tax_rate || row.tax_percentage}%` : null
|
|
488
|
+
].filter(Boolean).join("\n") || "—",
|
|
489
|
+
|
|
490
|
+
amount: fmtCurrency(row.estimated_total_cost || row.subtotal || row.line_total || row.total_cost || row.amount || row.grand_total),
|
|
491
|
+
amount_num: (row.estimated_total_cost || row.subtotal || row.line_total || row.total_cost || row.amount || row.grand_total)
|
|
492
|
+
? Number(row.estimated_total_cost || row.subtotal || row.line_total || row.total_cost || row.amount || row.grand_total).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
493
|
+
: "—"
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<div key={ri} style={{ display: "flex", flexWrap: "wrap", padding: ec.row_padding || "4px 8px", backgroundColor: ri % 2 === 1 ? stripeBg : "transparent", borderTop: rowSep !== "none" ? `1px ${rowSep} ${borderColor}` : "none", pageBreakInside: "avoid" }}>
|
|
498
|
+
{cols.map((col: any, ci: number) => {
|
|
499
|
+
let cellVal: any = "";
|
|
500
|
+
if (col.key) {
|
|
501
|
+
let val = rowDataCtx[col.key];
|
|
502
|
+
if (val === undefined || val === null) {
|
|
503
|
+
// Fallback to replaceTokens which now handles dot notation and item.* prefixes
|
|
504
|
+
val = replaceTokens(`{{${col.key}}}`, rowDataCtx);
|
|
505
|
+
}
|
|
506
|
+
cellVal = val ?? "";
|
|
507
|
+
} else if (col.content) {
|
|
508
|
+
cellVal = replaceTokens(col.content, rowDataCtx);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Format numbers if it looks like one and needs fixed decimals
|
|
512
|
+
if (typeof cellVal === 'number' || (typeof cellVal === 'string' && !isNaN(Number(cellVal)) && cellVal.includes('.'))) {
|
|
513
|
+
// Optional: Number(cellVal).toFixed(2);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return (
|
|
517
|
+
<div key={ci} style={{ width: `${col.width_percent}%`, fontSize: `${rowFs}px`, color: line.font_color || defaultColor, fontWeight: line.font_weight === "bold" ? 700 : line.font_weight === "light" ? 300 : (isA4 === false ? 600 : 400), textAlign: col.align || "left", whiteSpace: "pre-wrap", fontStyle: line.is_italic ? "italic" : "normal", textDecoration: line.is_underline ? "underline" : "none" }}>
|
|
518
|
+
{typeof cellVal === 'string' ? cellVal.split('\n').map((t: string, ti: number) => <div key={ti}>{t || <br />}</div>) : cellVal}
|
|
519
|
+
</div>
|
|
520
|
+
);
|
|
521
|
+
})}
|
|
522
|
+
</div>
|
|
523
|
+
);
|
|
524
|
+
})}
|
|
525
|
+
</div>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Barcode
|
|
530
|
+
if (line.line_type === "barcode") {
|
|
531
|
+
const rawVal = (line.extra_config || {}).value || "123456789";
|
|
532
|
+
const val = replaceTokens(rawVal, entityData);
|
|
533
|
+
const align = line.text_align || "center";
|
|
534
|
+
return (
|
|
535
|
+
<div className="print-barcode-container" style={{ marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px`, display: "flex", justifyContent: align === "left" ? "flex-start" : align === "right" ? "flex-end" : "center", maxWidth: line.max_width_percent ? `${line.max_width_percent}%` : "100%", overflow: "hidden", lineHeight: 0 }}>
|
|
536
|
+
<style>{`.print-barcode-container svg, .print-barcode-container img { max-width: 100%; height: auto; object-fit: contain; }`}</style>
|
|
537
|
+
<Barcode value={val || " "} renderer="svg" height={(line.extra_config || {}).height_px || 40} width={(line.extra_config || {}).bar_width || 1} fontSize={12} displayValue={(line.extra_config || {}).display_value !== false} margin={0} />
|
|
538
|
+
</div>
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// QR Code
|
|
543
|
+
if (line.line_type === "qrcode") {
|
|
544
|
+
const rawVal = (line.extra_config || {}).value || "https://example.com";
|
|
545
|
+
const val = replaceTokens(rawVal, entityData);
|
|
546
|
+
const align = line.text_align || "center";
|
|
547
|
+
return (
|
|
548
|
+
<div style={{ marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px`, display: "flex", justifyContent: align === "left" ? "flex-start" : align === "right" ? "flex-end" : "center" }}>
|
|
549
|
+
<QRCodeSVG value={val || " "} size={(line.extra_config || {}).size_px || 80} level="M" />
|
|
550
|
+
</div>
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Points (Cost Code Cipher)
|
|
555
|
+
if (line.line_type === "points") {
|
|
556
|
+
const ec = line.extra_config || {};
|
|
557
|
+
const rawVal = ec.value || "";
|
|
558
|
+
const path = rawVal.replace(/[{}]/g, '').trim();
|
|
559
|
+
|
|
560
|
+
let num = 0;
|
|
561
|
+
if (path === 'inventory_item_variants.cost_price' || path === 'cost_price') {
|
|
562
|
+
num = Number(entityData.cost_price || entityData.variants?.[0]?.cost_price || 0);
|
|
563
|
+
} else {
|
|
564
|
+
let val = entityData;
|
|
565
|
+
for (const k of path.split(".")) {
|
|
566
|
+
if (val == null) break;
|
|
567
|
+
val = val[k];
|
|
568
|
+
}
|
|
569
|
+
num = Number(val);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!isNaN(num) && num > 0) {
|
|
573
|
+
const map: Record<string, string> = {
|
|
574
|
+
'0': ec.map_0 || '0', '1': ec.map_1 || '1', '2': ec.map_2 || '2',
|
|
575
|
+
'3': ec.map_3 || '3', '4': ec.map_4 || '4', '5': ec.map_5 || '5',
|
|
576
|
+
'6': ec.map_6 || '6', '7': ec.map_7 || '7', '8': ec.map_8 || '8',
|
|
577
|
+
'9': ec.map_9 || '9',
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const repeatChar = ec.repeat_char || '';
|
|
581
|
+
const intStr = Math.round(num).toString();
|
|
582
|
+
|
|
583
|
+
let cipherText = "";
|
|
584
|
+
let lastChar = "";
|
|
585
|
+
|
|
586
|
+
for (let i = 0; i < intStr.length; i++) {
|
|
587
|
+
const char = intStr[i];
|
|
588
|
+
let mapped = map[char] || char;
|
|
589
|
+
if (repeatChar && mapped === lastChar) {
|
|
590
|
+
mapped = repeatChar;
|
|
591
|
+
}
|
|
592
|
+
cipherText += mapped;
|
|
593
|
+
lastChar = mapped;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const prefix = ec.prefix || "";
|
|
597
|
+
const suffix = ec.suffix || "";
|
|
598
|
+
return (
|
|
599
|
+
<div style={baseStyle}>
|
|
600
|
+
{prefix}{cipherText}{suffix}
|
|
601
|
+
</div>
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return null;
|
|
609
|
+
}
|