@apptimate/ui 6.1.0 → 6.3.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.
- package/package.json +1 -1
- package/src/base-components/Modal.tsx +1 -1
- package/src/base-components/Table.tsx +1 -1
- package/src/common-components/DashboardLayout.tsx +107 -79
- package/src/common-components/item-wizard/ItemFormWizard.tsx +35 -16
- package/src/common-components/print/TemplateRenderer.tsx +27 -6
package/package.json
CHANGED
|
@@ -102,7 +102,7 @@ export const Modal = ({ isOpen, onClose, title, children, className, footer, bac
|
|
|
102
102
|
{/* Body */}
|
|
103
103
|
<div className={cn(
|
|
104
104
|
"flex-1",
|
|
105
|
-
size === 'full' ? "overflow-hidden p-0" : "overflow-
|
|
105
|
+
size === 'full' ? "overflow-hidden p-0" : "overflow-auto px-5 sm:px-8 py-5 sm:py-7"
|
|
106
106
|
)}>
|
|
107
107
|
{children}
|
|
108
108
|
</div>
|
|
@@ -10,7 +10,7 @@ export interface TableProps {
|
|
|
10
10
|
|
|
11
11
|
export const Table = ({ children, className }: TableProps) => {
|
|
12
12
|
return (
|
|
13
|
-
<div className={cn("w-full", className)}>
|
|
13
|
+
<div className={cn("w-full overflow-x-auto", className)}>
|
|
14
14
|
<table className="w-full text-left lg:min-w-[600px] border-separate border-spacing-0 block lg:table">
|
|
15
15
|
{children}
|
|
16
16
|
</table>
|
|
@@ -6,7 +6,7 @@ import { Dropdown, DropdownItem } from '../base-components/Dropdown';
|
|
|
6
6
|
|
|
7
7
|
import Link from 'next/link';
|
|
8
8
|
import { usePathname, useRouter } from 'next/navigation';
|
|
9
|
-
import { cn } from '@apptimate/core-lib';
|
|
9
|
+
import { cn, getProjectConstructionGroups } from '@apptimate/core-lib';
|
|
10
10
|
|
|
11
11
|
export type DashboardMenuConfig = {
|
|
12
12
|
id: string;
|
|
@@ -75,7 +75,7 @@ export function DashboardLayout({
|
|
|
75
75
|
// Helper to build hierarchy
|
|
76
76
|
const flattenedOrganizations = React.useMemo(() => {
|
|
77
77
|
if (!organizations || organizations.length === 0) return [];
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
const orgMap = new Map<number, any>();
|
|
80
80
|
const roots: any[] = [];
|
|
81
81
|
|
|
@@ -103,14 +103,42 @@ export function DashboardLayout({
|
|
|
103
103
|
return flattened;
|
|
104
104
|
}, [organizations]);
|
|
105
105
|
|
|
106
|
+
|
|
107
|
+
// Override menus if it's a project organization
|
|
108
|
+
const effectiveMenus = React.useMemo(() => {
|
|
109
|
+
if (selectedOrganization?.organization_type !== 'project') return menus;
|
|
110
|
+
|
|
111
|
+
const projectMatch = pathname.match(/^(?:\/construction)?\/projects\/(\d+)(?:\/|$)/);
|
|
112
|
+
const projectId = projectMatch ? projectMatch[1] : 'current';
|
|
113
|
+
|
|
114
|
+
return menus.map(menu => {
|
|
115
|
+
if (menu.id === 'construction') {
|
|
116
|
+
return {
|
|
117
|
+
...menu,
|
|
118
|
+
groups: getProjectConstructionGroups(projectId)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return menu;
|
|
122
|
+
});
|
|
123
|
+
}, [menus, selectedOrganization, pathname]);
|
|
124
|
+
|
|
106
125
|
// Find which main menu should be active based on current path.
|
|
107
126
|
// When basePath is set, prefer the menu whose items are mostly within basePath.
|
|
108
127
|
const fullPath = basePath + (pathname === "/" && basePath ? "" : pathname);
|
|
109
128
|
const currentMainMenu = (() => {
|
|
110
|
-
let bestMenu =
|
|
129
|
+
let bestMenu = effectiveMenus[0];
|
|
130
|
+
|
|
131
|
+
// Default to construction if organization is a project
|
|
132
|
+
if (selectedOrganization?.organization_type === 'project') {
|
|
133
|
+
const constructionMenu = effectiveMenus.find(m => m.id === 'construction');
|
|
134
|
+
if (constructionMenu) {
|
|
135
|
+
bestMenu = constructionMenu;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
111
139
|
let bestScore = -1;
|
|
112
140
|
let bestInternalCount = -1;
|
|
113
|
-
for (const menu of
|
|
141
|
+
for (const menu of effectiveMenus) {
|
|
114
142
|
for (const group of menu.groups) {
|
|
115
143
|
for (const item of group.items) {
|
|
116
144
|
if (fullPath === item.path || fullPath.startsWith(item.path + "/")) {
|
|
@@ -138,7 +166,7 @@ export function DashboardLayout({
|
|
|
138
166
|
}
|
|
139
167
|
}, [currentMainMenu?.id]);
|
|
140
168
|
|
|
141
|
-
const activeMenu =
|
|
169
|
+
const activeMenu = effectiveMenus.find(m => m.id === activeMenuId) || effectiveMenus[0];
|
|
142
170
|
|
|
143
171
|
const handleMainMenuSelect = (menu: DashboardMenuConfig) => {
|
|
144
172
|
setActiveMenuId(menu.id);
|
|
@@ -218,7 +246,7 @@ export function DashboardLayout({
|
|
|
218
246
|
}
|
|
219
247
|
`}} />
|
|
220
248
|
<nav className="flex flex-col gap-6 flex-1 w-full sidebar-scroll pb-4">
|
|
221
|
-
{
|
|
249
|
+
{effectiveMenus.map((menu) => {
|
|
222
250
|
const isActive = activeMenuId === menu.id;
|
|
223
251
|
return (
|
|
224
252
|
<div
|
|
@@ -300,79 +328,79 @@ export function DashboardLayout({
|
|
|
300
328
|
<PanelLeftClose size={18} />
|
|
301
329
|
</button>
|
|
302
330
|
</div>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
</div>
|
|
348
|
-
))}
|
|
349
|
-
</div>
|
|
350
|
-
|
|
351
|
-
{/* Organization Selector - Bottom of Secondary Sidebar */}
|
|
352
|
-
{organizations.length > 0 && (
|
|
353
|
-
<div className="px-4 pt-4 mt-2 border-t border-gray-100">
|
|
354
|
-
<button
|
|
355
|
-
id="org-selector-desktop"
|
|
356
|
-
onClick={() => setIsOrgModalOpen(true)}
|
|
357
|
-
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl bg-[#F4F5F7] hover:bg-gray-200/70 transition-all duration-200 group cursor-pointer"
|
|
358
|
-
>
|
|
359
|
-
<div className={cn(
|
|
360
|
-
"w-8 h-8 rounded-lg flex items-center justify-center shrink-0 shadow-sm",
|
|
361
|
-
selectedOrganization?.organization_type === 'project'
|
|
362
|
-
? "bg-gradient-to-br from-teal-400 to-teal-600"
|
|
363
|
-
: "bg-gradient-to-br from-indigo-500 to-purple-600"
|
|
364
|
-
)}>
|
|
365
|
-
<Building2 size={14} className="text-white" />
|
|
331
|
+
<div className="flex-1 overflow-y-auto px-4 space-y-6">
|
|
332
|
+
{activeMenu.groups.map((group) => (
|
|
333
|
+
<div key={group.id}>
|
|
334
|
+
{group.label && (
|
|
335
|
+
<h3 className="px-2 text-xs font-bold text-gray-400 uppercase tracking-wider mb-2 empty:hidden">{group.label}</h3>
|
|
336
|
+
)}
|
|
337
|
+
<nav className="flex flex-col gap-1">
|
|
338
|
+
{group.items.map((item) => {
|
|
339
|
+
// Find the longest matching path in this group to avoid parent paths being active
|
|
340
|
+
const allGroupItems = activeMenu.groups.flatMap(g => g.items);
|
|
341
|
+
const matchingItems = allGroupItems.filter(i => fullPath === i.path || fullPath.startsWith(`${i.path}/`));
|
|
342
|
+
const longestMatch = matchingItems.sort((a, b) => b.path.length - a.path.length)[0];
|
|
343
|
+
const isItemActive = longestMatch?.path === item.path;
|
|
344
|
+
const isInternal = basePath
|
|
345
|
+
? item.path.startsWith(basePath)
|
|
346
|
+
: !externalPaths.some(ext => item.path.startsWith(ext));
|
|
347
|
+
const href = basePath && isInternal ? item.path.replace(basePath, "") || "/" : item.path;
|
|
348
|
+
|
|
349
|
+
const className = `px-3 py-2 rounded-lg text-sm transition-colors flex items-center justify-between ${isItemActive
|
|
350
|
+
? "bg-[#F4F5F7] text-[#2D3142] font-semibold"
|
|
351
|
+
: "text-gray-500 font-medium hover:bg-gray-50"
|
|
352
|
+
}`;
|
|
353
|
+
|
|
354
|
+
const content = (
|
|
355
|
+
<>
|
|
356
|
+
<div className="flex items-center gap-3">
|
|
357
|
+
{item.icon && <span className={`${isItemActive ? 'text-[#2D3142]' : 'text-gray-400'}`}>{item.icon}</span>}
|
|
358
|
+
<span>{item.label}</span>
|
|
359
|
+
</div>
|
|
360
|
+
{item.badge && <div>{item.badge}</div>}
|
|
361
|
+
</>
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
return isInternal ? (
|
|
365
|
+
<Link key={item.id} href={href} className={className}>
|
|
366
|
+
{content}
|
|
367
|
+
</Link>
|
|
368
|
+
) : (
|
|
369
|
+
<a key={item.id} href={href} className={className}>
|
|
370
|
+
{content}
|
|
371
|
+
</a>
|
|
372
|
+
);
|
|
373
|
+
})}
|
|
374
|
+
</nav>
|
|
366
375
|
</div>
|
|
367
|
-
|
|
368
|
-
<p className="text-[11px] font-semibold text-gray-400 uppercase tracking-wider leading-none mb-0.5">Organization</p>
|
|
369
|
-
<p className="text-[13px] font-bold text-[#2D3142] truncate leading-tight">{orgDisplayName}</p>
|
|
370
|
-
</div>
|
|
371
|
-
<ChevronRight size={14} className="text-gray-400 group-hover:text-[#2D3142] transition-colors shrink-0" />
|
|
372
|
-
</button>
|
|
376
|
+
))}
|
|
373
377
|
</div>
|
|
374
|
-
|
|
375
|
-
|
|
378
|
+
|
|
379
|
+
{/* Organization Selector - Bottom of Secondary Sidebar */}
|
|
380
|
+
{organizations.length > 0 && (
|
|
381
|
+
<div className="px-4 pt-4 mt-2 border-t border-gray-100">
|
|
382
|
+
<button
|
|
383
|
+
id="org-selector-desktop"
|
|
384
|
+
onClick={() => setIsOrgModalOpen(true)}
|
|
385
|
+
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl bg-[#F4F5F7] hover:bg-gray-200/70 transition-all duration-200 group cursor-pointer"
|
|
386
|
+
>
|
|
387
|
+
<div className={cn(
|
|
388
|
+
"w-8 h-8 rounded-lg flex items-center justify-center shrink-0 shadow-sm",
|
|
389
|
+
selectedOrganization?.organization_type === 'project'
|
|
390
|
+
? "bg-gradient-to-br from-teal-400 to-teal-600"
|
|
391
|
+
: "bg-gradient-to-br from-indigo-500 to-purple-600"
|
|
392
|
+
)}>
|
|
393
|
+
<Building2 size={14} className="text-white" />
|
|
394
|
+
</div>
|
|
395
|
+
<div className="flex-1 min-w-0 text-left">
|
|
396
|
+
<p className="text-[11px] font-semibold text-gray-400 uppercase tracking-wider leading-none mb-0.5">Organization</p>
|
|
397
|
+
<p className="text-[13px] font-bold text-[#2D3142] truncate leading-tight">{orgDisplayName}</p>
|
|
398
|
+
</div>
|
|
399
|
+
<ChevronRight size={14} className="text-gray-400 group-hover:text-[#2D3142] transition-colors shrink-0" />
|
|
400
|
+
</button>
|
|
401
|
+
</div>
|
|
402
|
+
)}
|
|
403
|
+
</aside>
|
|
376
404
|
|
|
377
405
|
</>
|
|
378
406
|
)}
|
|
@@ -451,7 +479,7 @@ export function DashboardLayout({
|
|
|
451
479
|
|
|
452
480
|
<div className="flex-1 overflow-y-auto py-6 px-4">
|
|
453
481
|
<nav className="flex flex-col gap-8">
|
|
454
|
-
{
|
|
482
|
+
{effectiveMenus.map((menu) => (
|
|
455
483
|
<div key={menu.id}>
|
|
456
484
|
<div className="flex items-center gap-3 text-[#2D3142] font-bold mb-3 px-2">
|
|
457
485
|
{React.cloneElement(menu.icon as React.ReactElement<any>, {
|
|
@@ -567,7 +595,7 @@ export function DashboardLayout({
|
|
|
567
595
|
const isSelected = selectedOrganization?.id === org.id;
|
|
568
596
|
const pl = org._level > 0 ? org._level * 24 : 0;
|
|
569
597
|
const isProject = org.organization_type === 'project';
|
|
570
|
-
|
|
598
|
+
|
|
571
599
|
return (
|
|
572
600
|
<button
|
|
573
601
|
key={org.id}
|
|
@@ -22,7 +22,7 @@ export interface ItemFormData {
|
|
|
22
22
|
category_id: string; category_name: string;
|
|
23
23
|
brand_id: string; brand_name: string;
|
|
24
24
|
description: string;
|
|
25
|
-
sale_price: string; cost_price: string;
|
|
25
|
+
sale_price: string; cost_price: string; min_sales_price: string;
|
|
26
26
|
has_variants: boolean; tracking_type: string; valuation_method: string;
|
|
27
27
|
// Page 2: Stock & Config
|
|
28
28
|
uom_id: string; uom_name: string;
|
|
@@ -41,7 +41,7 @@ export interface ItemFormData {
|
|
|
41
41
|
|
|
42
42
|
export interface VariantRow {
|
|
43
43
|
id?: number; sku: string; variant_name: string; barcode: string;
|
|
44
|
-
sale_price: string; cost_price: string; attribute_values: Record<string, string>;
|
|
44
|
+
sale_price: string; cost_price: string; min_sales_price: string; attribute_values: Record<string, string>;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export const emptyFormData: ItemFormData = {
|
|
@@ -49,7 +49,7 @@ export const emptyFormData: ItemFormData = {
|
|
|
49
49
|
category_id: "", category_name: "",
|
|
50
50
|
brand_id: "", brand_name: "",
|
|
51
51
|
description: "",
|
|
52
|
-
sale_price: "0", cost_price: "0",
|
|
52
|
+
sale_price: "0", cost_price: "0", min_sales_price: "0",
|
|
53
53
|
has_variants: false, tracking_type: "none", valuation_method: "fifo",
|
|
54
54
|
uom_id: "", uom_name: "", purchase_uom_id: "", sales_uom_id: "",
|
|
55
55
|
purchase_uom_conversion: "1", sales_uom_conversion: "1",
|
|
@@ -473,7 +473,7 @@ export default function ItemFormWizard({
|
|
|
473
473
|
const addVariantRow = () => {
|
|
474
474
|
setFormData((p) => ({
|
|
475
475
|
...p,
|
|
476
|
-
variants: [...p.variants, { sku: "", variant_name: "", barcode: "", sale_price: "", cost_price: "", attribute_values: {} }],
|
|
476
|
+
variants: [...p.variants, { sku: "", variant_name: "", barcode: "", sale_price: "", cost_price: "", min_sales_price: "", attribute_values: {} }],
|
|
477
477
|
}));
|
|
478
478
|
};
|
|
479
479
|
|
|
@@ -524,6 +524,7 @@ export default function ItemFormWizard({
|
|
|
524
524
|
barcode: "",
|
|
525
525
|
sale_price: formData.sale_price || "0",
|
|
526
526
|
cost_price: formData.cost_price || "0",
|
|
527
|
+
min_sales_price: formData.min_sales_price || "0",
|
|
527
528
|
attribute_values: combo,
|
|
528
529
|
};
|
|
529
530
|
});
|
|
@@ -543,13 +544,16 @@ export default function ItemFormWizard({
|
|
|
543
544
|
{!formData.has_variants ? (
|
|
544
545
|
<div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
|
|
545
546
|
<p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Item Pricing</p>
|
|
546
|
-
<div className="grid grid-cols-
|
|
547
|
+
<div className="grid grid-cols-3 gap-4">
|
|
547
548
|
{formData.is_available_sell && (
|
|
548
549
|
<Input label="Sale Price" type="number" placeholder="0.00" value={formData.sale_price} onChange={(e) => update("sale_price", e.target.value)} />
|
|
549
550
|
)}
|
|
550
551
|
{formData.is_available_purchase && (
|
|
551
552
|
<Input label="Cost Price" type="number" placeholder="0.00" value={formData.cost_price} onChange={(e) => update("cost_price", e.target.value)} />
|
|
552
553
|
)}
|
|
554
|
+
{formData.is_available_sell && (
|
|
555
|
+
<Input label="Min Sales Price" type="number" placeholder="0.00" value={formData.min_sales_price} onChange={(e) => update("min_sales_price", e.target.value)} />
|
|
556
|
+
)}
|
|
553
557
|
</div>
|
|
554
558
|
</div>
|
|
555
559
|
) : (
|
|
@@ -577,14 +581,24 @@ export default function ItemFormWizard({
|
|
|
577
581
|
</th>
|
|
578
582
|
<th className="px-4 py-3 font-medium align-top">Barcode</th>
|
|
579
583
|
{formData.is_available_sell && (
|
|
580
|
-
|
|
581
|
-
<
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
584
|
+
<>
|
|
585
|
+
<th className="px-4 py-3 font-medium w-[140px] align-top">
|
|
586
|
+
<div className="flex flex-col items-start gap-1">
|
|
587
|
+
<span>Sale Price</span>
|
|
588
|
+
{formData.variants.length > 1 && (
|
|
589
|
+
<button type="button" onClick={() => copyToAll("sale_price")} className="text-primary-600 hover:text-primary-700 font-semibold normal-case text-[11px]">Copy to All</button>
|
|
590
|
+
)}
|
|
591
|
+
</div>
|
|
592
|
+
</th>
|
|
593
|
+
<th className="px-4 py-3 font-medium w-[140px] align-top">
|
|
594
|
+
<div className="flex flex-col items-start gap-1">
|
|
595
|
+
<span>Min Sale Price</span>
|
|
596
|
+
{formData.variants.length > 1 && (
|
|
597
|
+
<button type="button" onClick={() => copyToAll("min_sales_price")} className="text-primary-600 hover:text-primary-700 font-semibold normal-case text-[11px]">Copy to All</button>
|
|
598
|
+
)}
|
|
599
|
+
</div>
|
|
600
|
+
</th>
|
|
601
|
+
</>
|
|
588
602
|
)}
|
|
589
603
|
{formData.is_available_purchase && (
|
|
590
604
|
<th className="px-4 py-3 font-medium w-[140px] align-top">
|
|
@@ -612,9 +626,14 @@ export default function ItemFormWizard({
|
|
|
612
626
|
<Input placeholder="Leave empty to auto-generate" value={v.barcode} onChange={(e) => updateVariant(idx, "barcode", e.target.value)} />
|
|
613
627
|
</td>
|
|
614
628
|
{formData.is_available_sell && (
|
|
615
|
-
|
|
616
|
-
<
|
|
617
|
-
|
|
629
|
+
<>
|
|
630
|
+
<td className="p-2 min-w-[120px] align-top">
|
|
631
|
+
<Input type="number" placeholder="0.00" value={v.sale_price} onChange={(e) => updateVariant(idx, "sale_price", e.target.value)} />
|
|
632
|
+
</td>
|
|
633
|
+
<td className="p-2 min-w-[120px] align-top">
|
|
634
|
+
<Input type="number" placeholder="0.00" value={v.min_sales_price} onChange={(e) => updateVariant(idx, "min_sales_price", e.target.value)} />
|
|
635
|
+
</td>
|
|
636
|
+
</>
|
|
618
637
|
)}
|
|
619
638
|
{formData.is_available_purchase && (
|
|
620
639
|
<td className="p-2 min-w-[120px] align-top">
|
|
@@ -213,6 +213,7 @@ export function replaceTokens(text: string, entityData: any): string {
|
|
|
213
213
|
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.sku") val = data.variants?.[0]?.sku || data.sku || data.code;
|
|
214
214
|
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.barcode") val = data.variants?.[0]?.barcode || data.barcode || data.sku || data.code;
|
|
215
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.min_sales_price" || path.trim() === "min_sales_price")) val = data.min_sales_price || data.variants?.[0]?.min_sales_price || 0;
|
|
216
217
|
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
218
|
if ((val === undefined || val === null) && path.trim() === "inventory_batches.batch_number") val = data.batch_number || data.batch?.batch_number || "";
|
|
218
219
|
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;
|
|
@@ -347,6 +348,15 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
347
348
|
wordBreak: "break-word" as const,
|
|
348
349
|
};
|
|
349
350
|
|
|
351
|
+
const ec = typeof line.extra_config === 'string' ? JSON.parse(line.extra_config || '{}') : (line.extra_config || {});
|
|
352
|
+
|
|
353
|
+
if (ec.truncate_single_line) {
|
|
354
|
+
baseStyle.whiteSpace = "nowrap";
|
|
355
|
+
baseStyle.overflow = "hidden";
|
|
356
|
+
baseStyle.textOverflow = "ellipsis";
|
|
357
|
+
baseStyle.display = "block";
|
|
358
|
+
}
|
|
359
|
+
|
|
350
360
|
if (line.border_style && line.border_style !== "none") {
|
|
351
361
|
const bw = `${line.border_width || 1}px`;
|
|
352
362
|
const bc = line.border_color || "#000000";
|
|
@@ -360,7 +370,11 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
360
370
|
|
|
361
371
|
// Text
|
|
362
372
|
if (line.line_type === "text") {
|
|
363
|
-
|
|
373
|
+
const content = replaceTokens(line.static_text || "", entityData).trim();
|
|
374
|
+
if (ec.truncate_single_line) {
|
|
375
|
+
return <div style={baseStyle}>{content}</div>;
|
|
376
|
+
}
|
|
377
|
+
return <div style={baseStyle}>{content.split("\n").map((t, i) => <div key={i}>{t || <br />}</div>)}</div>;
|
|
364
378
|
}
|
|
365
379
|
|
|
366
380
|
// Token (legacy)
|
|
@@ -405,11 +419,14 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
405
419
|
const childCols = line.extra_config?.columns || line.columns || [];
|
|
406
420
|
return (
|
|
407
421
|
<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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
422
|
+
{childCols.map((col: any, i: number) => {
|
|
423
|
+
const isTruncated = col.truncate_single_line || ec.truncate_single_line;
|
|
424
|
+
return (
|
|
425
|
+
<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: isTruncated ? "nowrap" : "pre-wrap", overflow: isTruncated ? "hidden" : "visible", textOverflow: isTruncated ? "ellipsis" : "clip", display: "block" }}>
|
|
426
|
+
{isTruncated ? replaceTokens(col.content || col.static_text || "", entityData) : replaceTokens(col.content || col.static_text || "", entityData).split("\n").map((t, ii) => <div key={ii}>{t || <br />}</div>)}
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
})}
|
|
413
430
|
</div>
|
|
414
431
|
);
|
|
415
432
|
}
|
|
@@ -560,6 +577,10 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
560
577
|
let num = 0;
|
|
561
578
|
if (path === 'inventory_item_variants.cost_price' || path === 'cost_price') {
|
|
562
579
|
num = Number(entityData.cost_price || entityData.variants?.[0]?.cost_price || 0);
|
|
580
|
+
} else if (path === 'inventory_item_variants.min_sales_price' || path === 'min_sales_price') {
|
|
581
|
+
num = Number(entityData.min_sales_price || entityData.variants?.[0]?.min_sales_price || 0);
|
|
582
|
+
} else if (path === 'inventory_item_variants.sale_price' || path === 'sale_price' || path === 'price') {
|
|
583
|
+
num = Number(entityData.sale_price || entityData.price || entityData.variants?.[0]?.sale_price || 0);
|
|
563
584
|
} else {
|
|
564
585
|
let val = entityData;
|
|
565
586
|
for (const k of path.split(".")) {
|