react-id-card-generator 1.0.21 → 1.1.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.
Files changed (45) hide show
  1. package/README.md +33 -12
  2. package/dist/cjs/components/IDCardDesigner.js +147 -10
  3. package/dist/cjs/components/IDCardDesigner.js.map +1 -1
  4. package/dist/cjs/components/IDCardGenerator.js +8 -3
  5. package/dist/cjs/components/IDCardGenerator.js.map +1 -1
  6. package/dist/cjs/components/IDCardPreview.js +3 -2
  7. package/dist/cjs/components/IDCardPreview.js.map +1 -1
  8. package/dist/cjs/components/IDCardTable.js +74 -0
  9. package/dist/cjs/components/IDCardTable.js.map +1 -0
  10. package/dist/cjs/index.js +4 -0
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/storage/templateStorage.js +2 -2
  13. package/dist/cjs/storage/templateStorage.js.map +1 -1
  14. package/dist/cjs/styles.css +1 -1
  15. package/dist/cjs/utils/styleUtils.js +3 -0
  16. package/dist/cjs/utils/styleUtils.js.map +1 -1
  17. package/dist/cjs/utils/tableUtils.js +106 -0
  18. package/dist/cjs/utils/tableUtils.js.map +1 -0
  19. package/dist/cjs/utils/validators.js +33 -10
  20. package/dist/cjs/utils/validators.js.map +1 -1
  21. package/dist/esm/components/IDCardDesigner.js +147 -10
  22. package/dist/esm/components/IDCardDesigner.js.map +1 -1
  23. package/dist/esm/components/IDCardGenerator.js +8 -3
  24. package/dist/esm/components/IDCardGenerator.js.map +1 -1
  25. package/dist/esm/components/IDCardPreview.js +3 -2
  26. package/dist/esm/components/IDCardPreview.js.map +1 -1
  27. package/dist/esm/components/IDCardTable.js +72 -0
  28. package/dist/esm/components/IDCardTable.js.map +1 -0
  29. package/dist/esm/index.js +1 -0
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/esm/storage/templateStorage.js +2 -2
  32. package/dist/esm/storage/templateStorage.js.map +1 -1
  33. package/dist/esm/styles.css +1 -1
  34. package/dist/esm/utils/styleUtils.js +3 -0
  35. package/dist/esm/utils/styleUtils.js.map +1 -1
  36. package/dist/esm/utils/tableUtils.js +96 -0
  37. package/dist/esm/utils/tableUtils.js.map +1 -0
  38. package/dist/esm/utils/validators.js +33 -10
  39. package/dist/esm/utils/validators.js.map +1 -1
  40. package/dist/index.d.ts +43 -4
  41. package/dist/types/components/IDCardTable.d.ts +8 -0
  42. package/dist/types/index.d.ts +2 -1
  43. package/dist/types/types/index.d.ts +37 -2
  44. package/dist/types/utils/tableUtils.d.ts +10 -0
  45. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_TABLE_ROW_COUNT = 5;
4
+ const DEFAULT_TABLE_COLUMN_COUNT = 5;
5
+ function createDefaultTableColumns(count = DEFAULT_TABLE_COLUMN_COUNT) {
6
+ return Array.from({ length: Math.max(1, count) }, (_, index) => ({
7
+ id: `column_${index + 1}`,
8
+ header: `Header ${index + 1}`,
9
+ fieldKey: `column${index + 1}`,
10
+ width: 1,
11
+ align: 'left',
12
+ }));
13
+ }
14
+ function createDefaultTableConfig() {
15
+ return {
16
+ rowCount: DEFAULT_TABLE_ROW_COUNT,
17
+ columns: createDefaultTableColumns(DEFAULT_TABLE_COLUMN_COUNT),
18
+ showHeader: true,
19
+ zebraRows: false,
20
+ style: {
21
+ borderColor: '#000000',
22
+ borderWidth: 1,
23
+ headerBackgroundColor: '#f0f0f0',
24
+ headerTextColor: '#000000',
25
+ headerFontSize: '10px',
26
+ headerFontWeight: 'bold',
27
+ bodyTextColor: '#000000',
28
+ bodyFontSize: '10px',
29
+ bodyFontWeight: 'normal',
30
+ cellPadding: '2px 4px',
31
+ zebraBackgroundColor: '#f7f7f7',
32
+ },
33
+ };
34
+ }
35
+ function resizeTableColumns(columns, count) {
36
+ const safeCount = Math.max(1, Math.floor(count || 1));
37
+ const existing = columns || [];
38
+ return Array.from({ length: safeCount }, (_, index) => {
39
+ const current = existing[index];
40
+ if (current) {
41
+ return {
42
+ ...current,
43
+ id: current.id || `column_${index + 1}`,
44
+ header: current.header ?? `Header ${index + 1}`,
45
+ fieldKey: current.fieldKey || `column${index + 1}`,
46
+ width: current.width && current.width > 0 ? current.width : 1,
47
+ align: current.align || 'left',
48
+ };
49
+ }
50
+ return createDefaultTableColumns(safeCount)[index];
51
+ });
52
+ }
53
+ function normalizeTableConfig(table) {
54
+ const defaults = createDefaultTableConfig();
55
+ const columns = resizeTableColumns(table?.columns?.length ? table.columns : defaults.columns, table?.columns?.length || DEFAULT_TABLE_COLUMN_COUNT);
56
+ return {
57
+ ...defaults,
58
+ ...table,
59
+ rowCount: Math.max(1, Math.floor(table?.rowCount || defaults.rowCount)),
60
+ columns,
61
+ showHeader: table?.showHeader ?? defaults.showHeader,
62
+ zebraRows: table?.zebraRows ?? defaults.zebraRows,
63
+ rowHeight: typeof table?.rowHeight === 'number' && table.rowHeight > 0
64
+ ? table.rowHeight
65
+ : undefined,
66
+ style: {
67
+ ...defaults.style,
68
+ ...table?.style,
69
+ borderWidth: typeof table?.style?.borderWidth === 'number' && table.style.borderWidth >= 0
70
+ ? table.style.borderWidth
71
+ : defaults.style?.borderWidth,
72
+ },
73
+ };
74
+ }
75
+ function coerceTableRows(value) {
76
+ if (!Array.isArray(value))
77
+ return [];
78
+ return value
79
+ .filter((row) => Boolean(row) && typeof row === 'object' && !Array.isArray(row))
80
+ .map((row) => ({ ...row }));
81
+ }
82
+ function getRenderedTableRows(data, fieldKey, table) {
83
+ const rows = coerceTableRows(data[fieldKey]);
84
+ const renderedRowCount = Math.max(table.rowCount, rows.length);
85
+ return Array.from({ length: renderedRowCount }, (_, index) => rows[index] || {});
86
+ }
87
+ function createSampleTableRows(tableConfig) {
88
+ const table = normalizeTableConfig(tableConfig);
89
+ return Array.from({ length: table.rowCount }, (_, rowIndex) => {
90
+ return table.columns.reduce((row, column) => {
91
+ row[column.fieldKey] = `${column.header} ${rowIndex + 1}`;
92
+ return row;
93
+ }, {});
94
+ });
95
+ }
96
+
97
+ exports.DEFAULT_TABLE_COLUMN_COUNT = DEFAULT_TABLE_COLUMN_COUNT;
98
+ exports.DEFAULT_TABLE_ROW_COUNT = DEFAULT_TABLE_ROW_COUNT;
99
+ exports.coerceTableRows = coerceTableRows;
100
+ exports.createDefaultTableColumns = createDefaultTableColumns;
101
+ exports.createDefaultTableConfig = createDefaultTableConfig;
102
+ exports.createSampleTableRows = createSampleTableRows;
103
+ exports.getRenderedTableRows = getRenderedTableRows;
104
+ exports.normalizeTableConfig = normalizeTableConfig;
105
+ exports.resizeTableColumns = resizeTableColumns;
106
+ //# sourceMappingURL=tableUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tableUtils.js","sources":["../../../src/utils/tableUtils.ts"],"sourcesContent":["import type { IDCardData, TableColumn, TableConfig, TableRowData } from '../types';\n\nexport const DEFAULT_TABLE_ROW_COUNT = 5;\nexport const DEFAULT_TABLE_COLUMN_COUNT = 5;\n\nexport function createDefaultTableColumns(count = DEFAULT_TABLE_COLUMN_COUNT): TableColumn[] {\n return Array.from({ length: Math.max(1, count) }, (_, index) => ({\n id: `column_${index + 1}`,\n header: `Header ${index + 1}`,\n fieldKey: `column${index + 1}`,\n width: 1,\n align: 'left',\n }));\n}\n\nexport function createDefaultTableConfig(): TableConfig {\n return {\n rowCount: DEFAULT_TABLE_ROW_COUNT,\n columns: createDefaultTableColumns(DEFAULT_TABLE_COLUMN_COUNT),\n showHeader: true,\n zebraRows: false,\n style: {\n borderColor: '#000000',\n borderWidth: 1,\n headerBackgroundColor: '#f0f0f0',\n headerTextColor: '#000000',\n headerFontSize: '10px',\n headerFontWeight: 'bold',\n bodyTextColor: '#000000',\n bodyFontSize: '10px',\n bodyFontWeight: 'normal',\n cellPadding: '2px 4px',\n zebraBackgroundColor: '#f7f7f7',\n },\n };\n}\n\nexport function resizeTableColumns(\n columns: TableColumn[] | undefined,\n count: number\n): TableColumn[] {\n const safeCount = Math.max(1, Math.floor(count || 1));\n const existing = columns || [];\n\n return Array.from({ length: safeCount }, (_, index) => {\n const current = existing[index];\n if (current) {\n return {\n ...current,\n id: current.id || `column_${index + 1}`,\n header: current.header ?? `Header ${index + 1}`,\n fieldKey: current.fieldKey || `column${index + 1}`,\n width: current.width && current.width > 0 ? current.width : 1,\n align: current.align || 'left',\n };\n }\n\n return createDefaultTableColumns(safeCount)[index];\n });\n}\n\nexport function normalizeTableConfig(table?: TableConfig): TableConfig {\n const defaults = createDefaultTableConfig();\n const columns = resizeTableColumns(\n table?.columns?.length ? table.columns : defaults.columns,\n table?.columns?.length || DEFAULT_TABLE_COLUMN_COUNT\n );\n\n return {\n ...defaults,\n ...table,\n rowCount: Math.max(1, Math.floor(table?.rowCount || defaults.rowCount)),\n columns,\n showHeader: table?.showHeader ?? defaults.showHeader,\n zebraRows: table?.zebraRows ?? defaults.zebraRows,\n rowHeight:\n typeof table?.rowHeight === 'number' && table.rowHeight > 0\n ? table.rowHeight\n : undefined,\n style: {\n ...defaults.style,\n ...table?.style,\n borderWidth:\n typeof table?.style?.borderWidth === 'number' && table.style.borderWidth >= 0\n ? table.style.borderWidth\n : defaults.style?.borderWidth,\n },\n };\n}\n\nexport function coerceTableRows(value: IDCardData[string]): TableRowData[] {\n if (!Array.isArray(value)) return [];\n\n return value\n .filter((row): row is TableRowData => Boolean(row) && typeof row === 'object' && !Array.isArray(row))\n .map((row) => ({ ...row }));\n}\n\nexport function getRenderedTableRows(data: IDCardData, fieldKey: string, table: TableConfig): TableRowData[] {\n const rows = coerceTableRows(data[fieldKey]);\n const renderedRowCount = Math.max(table.rowCount, rows.length);\n\n return Array.from({ length: renderedRowCount }, (_, index) => rows[index] || {});\n}\n\nexport function createSampleTableRows(tableConfig?: TableConfig): TableRowData[] {\n const table = normalizeTableConfig(tableConfig);\n\n return Array.from({ length: table.rowCount }, (_, rowIndex) => {\n return table.columns.reduce<TableRowData>((row, column) => {\n row[column.fieldKey] = `${column.header} ${rowIndex + 1}`;\n return row;\n }, {});\n });\n}\n"],"names":[],"mappings":"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"}
@@ -27,16 +27,7 @@ function validateTemplate(template) {
27
27
  errors.push({ field: 'cardSize.height', message: 'Card height must be positive' });
28
28
  }
29
29
  }
30
- // Validate backgrounds
31
- if (!template.backgrounds?.front && template.sides === 'single') {
32
- errors.push({ field: 'backgrounds.front', message: 'Front background is required' });
33
- }
34
- if (template.sides === 'double' && !template.backgrounds?.back) {
35
- errors.push({
36
- field: 'backgrounds.back',
37
- message: 'Back background is required for double-sided cards',
38
- });
39
- }
30
+ // Backgrounds are optional; blank card sides render with the default card background.
40
31
  // Validate fields
41
32
  if (!template.fields || template.fields.length === 0) {
42
33
  errors.push({ field: 'fields', message: 'At least one field is required' });
@@ -108,6 +99,38 @@ function validateField(field, index) {
108
99
  message: 'Label fields require static text',
109
100
  });
110
101
  }
102
+ if (field.type === 'table') {
103
+ if (!field.table) {
104
+ errors.push({
105
+ field: `${prefix}.table`,
106
+ message: 'Table fields require table configuration',
107
+ });
108
+ }
109
+ else {
110
+ if (!field.table.rowCount || field.table.rowCount <= 0) {
111
+ errors.push({
112
+ field: `${prefix}.table.rowCount`,
113
+ message: 'Table row count must be positive',
114
+ });
115
+ }
116
+ if (!field.table.columns || field.table.columns.length === 0) {
117
+ errors.push({
118
+ field: `${prefix}.table.columns`,
119
+ message: 'Table fields require at least one column',
120
+ });
121
+ }
122
+ else {
123
+ field.table.columns.forEach((column, columnIndex) => {
124
+ if (!column.fieldKey) {
125
+ errors.push({
126
+ field: `${prefix}.table.columns[${columnIndex}].fieldKey`,
127
+ message: 'Table columns require field keys',
128
+ });
129
+ }
130
+ });
131
+ }
132
+ }
133
+ }
111
134
  return errors;
112
135
  }
113
136
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"validators.js","sources":["../../../src/utils/validators.ts"],"sourcesContent":["import type { IDCardTemplate, FieldMapping, CardSize } from '../types';\r\n\r\nexport interface ValidationError {\r\n field: string;\r\n message: string;\r\n}\r\n\r\n/**\r\n * Validate a complete ID card template\r\n * Checks for required fields, valid dimensions, and proper field configuration\r\n * @param template - The template to validate\r\n * @returns Array of validation errors (empty if valid)\r\n */\r\nexport function validateTemplate(template: IDCardTemplate): ValidationError[] {\r\n const errors: ValidationError[] = [];\r\n\r\n // Check required metadata\r\n if (!template.id) {\r\n errors.push({ field: 'id', message: 'Template ID is required' });\r\n }\r\n\r\n if (!template.name) {\r\n errors.push({ field: 'name', message: 'Template name is required' });\r\n }\r\n\r\n // Validate card dimensions\r\n if (!template.cardSize) {\r\n errors.push({ field: 'cardSize', message: 'Card size is required' });\r\n } else {\r\n if (template.cardSize.width <= 0) {\r\n errors.push({ field: 'cardSize.width', message: 'Card width must be positive' });\r\n }\r\n if (template.cardSize.height <= 0) {\r\n errors.push({ field: 'cardSize.height', message: 'Card height must be positive' });\r\n }\r\n }\r\n\r\n // Validate backgrounds\r\n if (!template.backgrounds?.front && template.sides === 'single') {\r\n errors.push({ field: 'backgrounds.front', message: 'Front background is required' });\r\n }\r\n\r\n if (template.sides === 'double' && !template.backgrounds?.back) {\r\n errors.push({\r\n field: 'backgrounds.back',\r\n message: 'Back background is required for double-sided cards',\r\n });\r\n }\r\n\r\n // Validate fields\r\n if (!template.fields || template.fields.length === 0) {\r\n errors.push({ field: 'fields', message: 'At least one field is required' });\r\n } else {\r\n template.fields.forEach((field, index) => {\r\n const fieldErrors = validateField(field, index);\r\n errors.push(...fieldErrors);\r\n });\r\n }\r\n\r\n // Ensure no duplicate field IDs (would cause rendering issues)\r\n const fieldIds = template.fields?.map((f) => f.id) || [];\r\n const duplicateIds = fieldIds.filter((id, index) => fieldIds.indexOf(id) !== index);\r\n if (duplicateIds.length > 0) {\r\n errors.push({\r\n field: 'fields',\r\n message: `Duplicate field IDs: ${[...new Set(duplicateIds)].join(', ')}`,\r\n });\r\n }\r\n\r\n return errors;\r\n}\r\n\r\n/**\r\n * Validate a single field's configuration\r\n * @param field - The field to validate\r\n * @param index - Field index (for error messages)\r\n * @returns Array of validation errors\r\n */\r\nexport function validateField(field: FieldMapping, index: number): ValidationError[] {\r\n const errors: ValidationError[] = [];\r\n const prefix = `fields[${index}]`;\r\n\r\n if (!field.id) {\r\n errors.push({ field: `${prefix}.id`, message: 'Field ID is required' });\r\n }\r\n\r\n if (!field.fieldKey) {\r\n errors.push({ field: `${prefix}.fieldKey`, message: 'Field key is required' });\r\n }\r\n\r\n if (!field.type) {\r\n errors.push({ field: `${prefix}.type`, message: 'Field type is required' });\r\n }\r\n\r\n if (!field.side) {\r\n errors.push({ field: `${prefix}.side`, message: 'Field side is required' });\r\n }\r\n\r\n if (!field.position) {\r\n errors.push({ field: `${prefix}.position`, message: 'Field position is required' });\r\n } else {\r\n if (field.position.x < 0) {\r\n errors.push({ field: `${prefix}.position.x`, message: 'X position cannot be negative' });\r\n }\r\n if (field.position.y < 0) {\r\n errors.push({ field: `${prefix}.position.y`, message: 'Y position cannot be negative' });\r\n }\r\n if (field.position.width <= 0) {\r\n errors.push({ field: `${prefix}.position.width`, message: 'Width must be positive' });\r\n }\r\n if (field.position.height <= 0) {\r\n errors.push({ field: `${prefix}.position.height`, message: 'Height must be positive' });\r\n }\r\n }\r\n\r\n\r\n\r\n if (field.type === 'qrcode' && (!field.qrFields || field.qrFields.length === 0)) {\r\n errors.push({\r\n field: `${prefix}.qrFields`,\r\n message: 'QR code fields require at least one data field',\r\n });\r\n }\r\n\r\n if (field.type === 'label' && !field.staticText && !field.label) {\r\n errors.push({\r\n field: `${prefix}.staticText`,\r\n message: 'Label fields require static text',\r\n });\r\n }\r\n\r\n return errors;\r\n}\r\n\r\n/**\r\n * Validate card size against standard dimensions\r\n */\r\nexport function validateCardSize(size: CardSize): boolean {\r\n const standardSizes = {\r\n 'CR80': { width: 85.6, height: 53.98, unit: 'mm' },\r\n 'CR79': { width: 83.9, height: 51.0, unit: 'mm' },\r\n 'CR100': { width: 98.0, height: 67.0, unit: 'mm' },\r\n };\r\n\r\n // Just ensure dimensions are reasonable\r\n if (size.unit === 'mm') {\r\n return size.width >= 30 && size.width <= 200 && size.height >= 30 && size.height <= 200;\r\n }\r\n if (size.unit === 'px') {\r\n return size.width >= 100 && size.width <= 2000 && size.height >= 100 && size.height <= 2000;\r\n }\r\n if (size.unit === 'in') {\r\n return size.width >= 1 && size.width <= 10 && size.height >= 1 && size.height <= 10;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Check if a field is within card bounds\r\n */\r\nexport function isFieldInBounds(\r\n field: FieldMapping,\r\n cardSize: CardSize\r\n): boolean {\r\n const { x, y, width, height } = field.position;\r\n return (\r\n x >= 0 &&\r\n y >= 0 &&\r\n x + width <= cardSize.width &&\r\n y + height <= cardSize.height\r\n );\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validators.js","sources":["../../../src/utils/validators.ts"],"sourcesContent":["import type { IDCardTemplate, FieldMapping, CardSize } from '../types';\r\n\r\nexport interface ValidationError {\r\n field: string;\r\n message: string;\r\n}\r\n\r\n/**\r\n * Validate a complete ID card template\r\n * Checks for required fields, valid dimensions, and proper field configuration\r\n * @param template - The template to validate\r\n * @returns Array of validation errors (empty if valid)\r\n */\r\nexport function validateTemplate(template: IDCardTemplate): ValidationError[] {\r\n const errors: ValidationError[] = [];\r\n\r\n // Check required metadata\r\n if (!template.id) {\r\n errors.push({ field: 'id', message: 'Template ID is required' });\r\n }\r\n\r\n if (!template.name) {\r\n errors.push({ field: 'name', message: 'Template name is required' });\r\n }\r\n\r\n // Validate card dimensions\r\n if (!template.cardSize) {\r\n errors.push({ field: 'cardSize', message: 'Card size is required' });\r\n } else {\r\n if (template.cardSize.width <= 0) {\r\n errors.push({ field: 'cardSize.width', message: 'Card width must be positive' });\r\n }\r\n if (template.cardSize.height <= 0) {\r\n errors.push({ field: 'cardSize.height', message: 'Card height must be positive' });\r\n }\r\n }\r\n\r\n // Backgrounds are optional; blank card sides render with the default card background.\n\r\n // Validate fields\r\n if (!template.fields || template.fields.length === 0) {\r\n errors.push({ field: 'fields', message: 'At least one field is required' });\r\n } else {\r\n template.fields.forEach((field, index) => {\r\n const fieldErrors = validateField(field, index);\r\n errors.push(...fieldErrors);\r\n });\r\n }\r\n\r\n // Ensure no duplicate field IDs (would cause rendering issues)\r\n const fieldIds = template.fields?.map((f) => f.id) || [];\r\n const duplicateIds = fieldIds.filter((id, index) => fieldIds.indexOf(id) !== index);\r\n if (duplicateIds.length > 0) {\r\n errors.push({\r\n field: 'fields',\r\n message: `Duplicate field IDs: ${[...new Set(duplicateIds)].join(', ')}`,\r\n });\r\n }\r\n\r\n return errors;\r\n}\r\n\r\n/**\r\n * Validate a single field's configuration\r\n * @param field - The field to validate\r\n * @param index - Field index (for error messages)\r\n * @returns Array of validation errors\r\n */\r\nexport function validateField(field: FieldMapping, index: number): ValidationError[] {\r\n const errors: ValidationError[] = [];\r\n const prefix = `fields[${index}]`;\r\n\r\n if (!field.id) {\r\n errors.push({ field: `${prefix}.id`, message: 'Field ID is required' });\r\n }\r\n\r\n if (!field.fieldKey) {\r\n errors.push({ field: `${prefix}.fieldKey`, message: 'Field key is required' });\r\n }\r\n\r\n if (!field.type) {\r\n errors.push({ field: `${prefix}.type`, message: 'Field type is required' });\r\n }\r\n\r\n if (!field.side) {\r\n errors.push({ field: `${prefix}.side`, message: 'Field side is required' });\r\n }\r\n\r\n if (!field.position) {\r\n errors.push({ field: `${prefix}.position`, message: 'Field position is required' });\r\n } else {\r\n if (field.position.x < 0) {\r\n errors.push({ field: `${prefix}.position.x`, message: 'X position cannot be negative' });\r\n }\r\n if (field.position.y < 0) {\r\n errors.push({ field: `${prefix}.position.y`, message: 'Y position cannot be negative' });\r\n }\r\n if (field.position.width <= 0) {\r\n errors.push({ field: `${prefix}.position.width`, message: 'Width must be positive' });\r\n }\r\n if (field.position.height <= 0) {\r\n errors.push({ field: `${prefix}.position.height`, message: 'Height must be positive' });\r\n }\r\n }\r\n\r\n\r\n\r\n if (field.type === 'qrcode' && (!field.qrFields || field.qrFields.length === 0)) {\r\n errors.push({\r\n field: `${prefix}.qrFields`,\r\n message: 'QR code fields require at least one data field',\r\n });\r\n }\r\n\r\n if (field.type === 'label' && !field.staticText && !field.label) {\n errors.push({\n field: `${prefix}.staticText`,\n message: 'Label fields require static text',\n });\n }\n\n if (field.type === 'table') {\n if (!field.table) {\n errors.push({\n field: `${prefix}.table`,\n message: 'Table fields require table configuration',\n });\n } else {\n if (!field.table.rowCount || field.table.rowCount <= 0) {\n errors.push({\n field: `${prefix}.table.rowCount`,\n message: 'Table row count must be positive',\n });\n }\n\n if (!field.table.columns || field.table.columns.length === 0) {\n errors.push({\n field: `${prefix}.table.columns`,\n message: 'Table fields require at least one column',\n });\n } else {\n field.table.columns.forEach((column, columnIndex) => {\n if (!column.fieldKey) {\n errors.push({\n field: `${prefix}.table.columns[${columnIndex}].fieldKey`,\n message: 'Table columns require field keys',\n });\n }\n });\n }\n }\n }\n\n return errors;\n}\n\r\n/**\r\n * Validate card size against standard dimensions\r\n */\r\nexport function validateCardSize(size: CardSize): boolean {\r\n const standardSizes = {\r\n 'CR80': { width: 85.6, height: 53.98, unit: 'mm' },\r\n 'CR79': { width: 83.9, height: 51.0, unit: 'mm' },\r\n 'CR100': { width: 98.0, height: 67.0, unit: 'mm' },\r\n };\r\n\r\n // Just ensure dimensions are reasonable\r\n if (size.unit === 'mm') {\r\n return size.width >= 30 && size.width <= 200 && size.height >= 30 && size.height <= 200;\r\n }\r\n if (size.unit === 'px') {\r\n return size.width >= 100 && size.width <= 2000 && size.height >= 100 && size.height <= 2000;\r\n }\r\n if (size.unit === 'in') {\r\n return size.width >= 1 && size.width <= 10 && size.height >= 1 && size.height <= 10;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Check if a field is within card bounds\r\n */\r\nexport function isFieldInBounds(\r\n field: FieldMapping,\r\n cardSize: CardSize\r\n): boolean {\r\n const { x, y, width, height } = field.position;\r\n return (\r\n x >= 0 &&\r\n y >= 0 &&\r\n x + width <= cardSize.width &&\r\n y + height <= cardSize.height\r\n );\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { useIDCardTemplate } from '../hooks/useIDCardTemplate.js';
4
4
  import { deepCleanUndefined } from '../utils/deepCleanUndefined.js';
5
5
  import { IDCardPreview } from './IDCardPreview.js';
6
6
  import { generateId, imageFileToBase64 } from '../storage/templateStorage.js';
7
+ import { createSampleTableRows, createDefaultTableConfig, normalizeTableConfig, resizeTableColumns, coerceTableRows } from '../utils/tableUtils.js';
7
8
 
8
9
  // Available field types in the designer
9
10
  const defaultFieldTypes = [
@@ -11,6 +12,7 @@ const defaultFieldTypes = [
11
12
  { type: 'label', label: 'Static Label', icon: '🏷️' },
12
13
  { type: 'image', label: 'Image/Photo', icon: '🖼️' },
13
14
  { type: 'qrcode', label: 'QR Code', icon: '📱' },
15
+ { type: 'table', label: 'Table', icon: '▦' },
14
16
  ];
15
17
  /**
16
18
  * IDCardDesigner - Visual drag-and-drop template builder
@@ -55,6 +57,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
55
57
  bloodgroup: 'O+',
56
58
  phoneno: '9876543210',
57
59
  address: 'Sample Address',
60
+ results: createSampleTableRows(createDefaultTableConfig()),
58
61
  });
59
62
  // Get selected field
60
63
  const selectedField = template.fields.find((f) => f.id === selectedFieldId);
@@ -82,9 +85,10 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
82
85
  }, [selectedField, addField, setSelectedFieldId, template.cardSize.width, template.cardSize.height]);
83
86
  // Handle adding new field
84
87
  const handleAddField = useCallback((type) => {
88
+ const tableConfig = type === 'table' ? createDefaultTableConfig() : undefined;
85
89
  const newField = {
86
90
  id: generateId('field'),
87
- fieldKey: `field_${Date.now()}`,
91
+ fieldKey: type === 'table' ? 'results' : `field_${Date.now()}`,
88
92
  label: type === 'label' ? 'Label' : '',
89
93
  type,
90
94
  side: activeSide,
@@ -98,6 +102,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
98
102
  },
99
103
  zIndex: template.fields.length + 1,
100
104
  staticText: type === 'label' ? 'Static Label' : '',
105
+ ...(tableConfig ? { table: tableConfig } : {}),
101
106
  ...(type === 'qrcode' ? { qrFields: [] } : {}),
102
107
  };
103
108
  // Adjust default sizes based on type
@@ -107,9 +112,24 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
107
112
  else if (type === 'qrcode') {
108
113
  newField.position = { x: 50, y: 50, width: 80, height: 80 };
109
114
  }
115
+ else if (type === 'table') {
116
+ const availableTableWidth = Math.max(20, template.cardSize.width - 40);
117
+ newField.position = {
118
+ x: 20,
119
+ y: 40,
120
+ width: Math.min(availableTableWidth, 300),
121
+ height: 120,
122
+ };
123
+ }
110
124
  addField(newField);
125
+ if (type === 'table' && tableConfig) {
126
+ setSampleData((prev) => ({
127
+ ...prev,
128
+ [newField.fieldKey]: prev[newField.fieldKey] || createSampleTableRows(tableConfig),
129
+ }));
130
+ }
111
131
  setSelectedFieldId(newField.id);
112
- }, [activeSide, addField, template.fields.length]);
132
+ }, [activeSide, addField, template.cardSize.width, template.fields.length]);
113
133
  // Handle background upload
114
134
  const handleBackgroundUpload = useCallback(async (e) => {
115
135
  const file = e.target.files?.[0];
@@ -127,8 +147,8 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
127
147
  const handleOrientationChange = useCallback((orientation) => {
128
148
  setOrientation(orientation);
129
149
  // Update card size based on orientation
130
- const baseWidth = 340;
131
- const baseHeight = 215;
150
+ const baseWidth = 640;
151
+ const baseHeight = 415;
132
152
  if (orientation === 'portrait') {
133
153
  setCardSize({ width: baseHeight, height: baseWidth, unit: 'px' });
134
154
  }
@@ -251,6 +271,19 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
251
271
  });
252
272
  }
253
273
  else {
274
+ if (property === 'fieldKey' && selectedField.type === 'table' && typeof value === 'string') {
275
+ updateField(selectedField.id, { fieldKey: value });
276
+ setSampleData((prev) => {
277
+ if (!value || value === selectedField.fieldKey || prev[value] !== undefined) {
278
+ return prev;
279
+ }
280
+ const next = { ...prev };
281
+ next[value] = prev[selectedField.fieldKey] || createSampleTableRows(selectedField.table);
282
+ delete next[selectedField.fieldKey];
283
+ return next;
284
+ });
285
+ return;
286
+ }
254
287
  // Only allow qrFields property on QR code fields
255
288
  if (property === 'qrFields') {
256
289
  if (selectedField.type === 'qrcode') {
@@ -262,6 +295,73 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
262
295
  }
263
296
  }
264
297
  }, [selectedField, updateField]);
298
+ const handleTableConfigUpdate = useCallback((updates) => {
299
+ if (!selectedField || selectedField.type !== 'table')
300
+ return;
301
+ const table = normalizeTableConfig(selectedField.table);
302
+ updateField(selectedField.id, {
303
+ table: {
304
+ ...table,
305
+ ...updates,
306
+ style: {
307
+ ...table.style,
308
+ ...updates.style,
309
+ },
310
+ },
311
+ });
312
+ }, [selectedField, updateField]);
313
+ const handleTableStyleUpdate = useCallback((styleKey, value) => {
314
+ if (!selectedField || selectedField.type !== 'table')
315
+ return;
316
+ const table = normalizeTableConfig(selectedField.table);
317
+ handleTableConfigUpdate({
318
+ style: {
319
+ ...table.style,
320
+ [styleKey]: value,
321
+ },
322
+ });
323
+ }, [selectedField, handleTableConfigUpdate]);
324
+ const handleTableColumnUpdate = useCallback((columnIndex, updates) => {
325
+ if (!selectedField || selectedField.type !== 'table')
326
+ return;
327
+ const table = normalizeTableConfig(selectedField.table);
328
+ const columns = table.columns.map((column, index) => index === columnIndex ? { ...column, ...updates } : column);
329
+ handleTableConfigUpdate({ columns });
330
+ }, [selectedField, handleTableConfigUpdate]);
331
+ const handleTableColumnCountUpdate = useCallback((count) => {
332
+ if (!selectedField || selectedField.type !== 'table')
333
+ return;
334
+ const table = normalizeTableConfig(selectedField.table);
335
+ handleTableConfigUpdate({
336
+ columns: resizeTableColumns(table.columns, count),
337
+ });
338
+ }, [selectedField, handleTableConfigUpdate]);
339
+ const handleTableSampleCellUpdate = useCallback((field, table, rowIndex, columnKey, value) => {
340
+ setSampleData((prev) => {
341
+ const existingRows = coerceTableRows(prev[field.fieldKey]);
342
+ const rowCount = Math.max(table.rowCount, existingRows.length, rowIndex + 1);
343
+ const rows = Array.from({ length: rowCount }, (_, index) => ({
344
+ ...(existingRows[index] || {}),
345
+ }));
346
+ rows[rowIndex] = {
347
+ ...rows[rowIndex],
348
+ [columnKey]: value,
349
+ };
350
+ return {
351
+ ...prev,
352
+ [field.fieldKey]: rows,
353
+ };
354
+ });
355
+ }, []);
356
+ const handleAddTableSampleRow = useCallback((field) => {
357
+ setSampleData((prev) => {
358
+ const rows = coerceTableRows(prev[field.fieldKey]);
359
+ return {
360
+ ...prev,
361
+ [field.fieldKey]: [...rows, {}],
362
+ };
363
+ });
364
+ }, []);
265
365
  // Handle save
266
366
  const handleSave = useCallback(() => {
267
367
  const updatedTemplate = {
@@ -337,28 +437,65 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
337
437
  document.addEventListener('keydown', handleKeyDown);
338
438
  return () => document.removeEventListener('keydown', handleKeyDown);
339
439
  }, [selectedFieldId, handleDeleteField, template.fields, template.cardSize, updateField]);
340
- return (jsxs("div", { className: `idcard-designer ${className}`, style: style, children: [jsxs("div", { className: "designer-header", children: [jsx("input", { type: "text", value: templateName, onChange: (e) => setTemplateName(e.target.value), className: "template-name-input", placeholder: "Template Name" }), jsxs("div", { className: "header-actions", children: [onCancel && (jsx("button", { onClick: onCancel, className: "btn btn-secondary", children: "Cancel" })), jsx("button", { onClick: handleSave, className: "btn btn-primary", disabled: !isValid, children: "Save Template" })] })] }), jsxs("div", { className: "designer-body", children: [jsxs("div", { className: "designer-sidebar left-sidebar", children: [jsx("h3", { children: "Add Fields" }), jsx("div", { className: "field-types", children: defaultFieldTypes.map((fieldType) => (jsxs("button", { onClick: () => handleAddField(fieldType.type), className: "field-type-btn", children: [jsx("span", { className: "field-icon", children: fieldType.icon }), jsx("span", { className: "field-label", children: fieldType.label })] }, fieldType.type))) }), jsx("h3", { children: "Card Settings" }), jsxs("div", { className: "card-settings", children: [jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Orientation" }), jsxs("div", { className: "btn-group", children: [jsx("button", { className: `btn ${template.orientation === 'landscape' ? 'active' : ''}`, onClick: () => handleOrientationChange('landscape'), children: "Landscape" }), jsx("button", { className: `btn ${template.orientation === 'portrait' ? 'active' : ''}`, onClick: () => handleOrientationChange('portrait'), children: "Portrait" })] })] }), jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Card Sides" }), jsxs("div", { className: "btn-group", children: [jsx("button", { className: `btn ${template.sides === 'single' ? 'active' : ''}`, onClick: () => handleSidesChange('single'), children: "Single" }), jsx("button", { className: `btn ${template.sides === 'double' ? 'active' : ''}`, onClick: () => handleSidesChange('double'), children: "Double" })] })] }), jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Card Size (px)" }), jsxs("div", { className: "size-inputs", children: [jsx("input", { type: "number", value: template.cardSize.width, onChange: (e) => setCardSize({ ...template.cardSize, width: Number(e.target.value) }), placeholder: "Width" }), jsx("span", { children: "\u00D7" }), jsx("input", { type: "number", value: template.cardSize.height, onChange: (e) => setCardSize({ ...template.cardSize, height: Number(e.target.value) }), placeholder: "Height" })] })] })] }), jsx("h3", { children: "Background" }), jsxs("div", { className: "background-upload", children: [jsx("input", { type: "file", accept: "image/*", onChange: handleBackgroundUpload, id: "bg-upload", className: "file-input" }), jsxs("label", { htmlFor: "bg-upload", className: "btn btn-outline", children: ["Upload ", activeSide, " Background"] }), template.backgrounds[activeSide] && (jsx("button", { className: "btn btn-danger btn-sm", onClick: () => setBackground(activeSide, ''), children: "Remove" }))] })] }), jsxs("div", { className: "designer-preview", children: [jsxs("div", { className: "side-tabs", children: [jsx("button", { className: `tab ${activeSide === 'front' ? 'active' : ''}`, onClick: () => setActiveSide('front'), children: "Front" }), template.sides === 'double' && (jsx("button", { className: `tab ${activeSide === 'back' ? 'active' : ''}`, onClick: () => setActiveSide('back'), children: "Back" })), jsxs("label", { className: "grid-toggle", children: [jsx("input", { type: "checkbox", checked: showGrid, onChange: (e) => setShowGrid(e.target.checked) }), "Show Grid"] })] }), jsx("div", { ref: previewContainerRef, className: "preview-wrapper", onMouseDown: handleMouseDown, children: jsx(IDCardPreview, { template: template, data: sampleData, side: activeSide, showGrid: showGrid, onFieldSelect: handleFieldSelect, selectedFieldId: selectedFieldId, editable: true }) }), jsxs("div", { className: "fields-list", children: [jsxs("h4", { children: ["Fields on ", activeSide, " side:"] }), template.fields
440
+ return (jsxs("div", { className: `idcard-designer ${className}`, style: style, children: [jsxs("div", { className: "designer-header", children: [jsx("input", { type: "text", value: templateName, onChange: (e) => setTemplateName(e.target.value), className: "template-name-input", placeholder: "Template Name" }), jsxs("div", { className: "header-actions", children: [onCancel && (jsx("button", { type: "button", onClick: onCancel, className: "btn btn-secondary", children: "Cancel" })), jsx("button", { type: "button", onClick: handleSave, className: "btn btn-primary", disabled: !isValid, children: "Save Template" })] })] }), jsxs("div", { className: "designer-body", children: [jsxs("div", { className: "designer-sidebar left-sidebar", children: [jsx("h3", { children: "Add Fields" }), jsx("div", { className: "field-types", children: defaultFieldTypes.map((fieldType) => (jsxs("button", { type: "button", onClick: () => handleAddField(fieldType.type), className: "field-type-btn", children: [jsx("span", { className: "field-icon", children: fieldType.icon }), jsx("span", { className: "field-label", children: fieldType.label })] }, fieldType.type))) }), jsx("h3", { children: "Card Settings" }), jsxs("div", { className: "card-settings", children: [jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Orientation" }), jsxs("div", { className: "btn-group", children: [jsx("button", { type: "button", className: `btn ${template.orientation === 'landscape' ? 'active' : ''}`, onClick: () => handleOrientationChange('landscape'), children: "Landscape" }), jsx("button", { type: "button", className: `btn ${template.orientation === 'portrait' ? 'active' : ''}`, onClick: () => handleOrientationChange('portrait'), children: "Portrait" })] })] }), jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Card Sides" }), jsxs("div", { className: "btn-group", children: [jsx("button", { type: "button", className: `btn ${template.sides === 'single' ? 'active' : ''}`, onClick: () => handleSidesChange('single'), children: "Single" }), jsx("button", { type: "button", className: `btn ${template.sides === 'double' ? 'active' : ''}`, onClick: () => handleSidesChange('double'), children: "Double" })] })] }), jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Card Size (px)" }), jsxs("div", { className: "size-inputs", children: [jsx("input", { type: "number", value: template.cardSize.width, onChange: (e) => setCardSize({ ...template.cardSize, width: Number(e.target.value) }), placeholder: "Width" }), jsx("span", { children: "\u00D7" }), jsx("input", { type: "number", value: template.cardSize.height, onChange: (e) => setCardSize({ ...template.cardSize, height: Number(e.target.value) }), placeholder: "Height" })] })] })] }), jsx("h3", { children: "Background" }), jsxs("div", { className: "background-upload", children: [jsx("input", { type: "file", accept: "image/*", onChange: handleBackgroundUpload, id: "bg-upload", className: "file-input" }), jsxs("label", { htmlFor: "bg-upload", className: "btn btn-outline", children: ["Upload ", activeSide, " Background"] }), template.backgrounds[activeSide] && (jsx("button", { type: "button", className: "btn btn-danger btn-sm", onClick: () => setBackground(activeSide, ''), children: "Remove" }))] })] }), jsxs("div", { className: "designer-preview", children: [jsxs("div", { className: "side-tabs", children: [jsx("button", { type: "button", className: `tab ${activeSide === 'front' ? 'active' : ''}`, onClick: () => setActiveSide('front'), children: "Front" }), template.sides === 'double' && (jsx("button", { type: "button", className: `tab ${activeSide === 'back' ? 'active' : ''}`, onClick: () => setActiveSide('back'), children: "Back" })), jsxs("label", { className: "grid-toggle", children: [jsx("input", { type: "checkbox", checked: showGrid, onChange: (e) => setShowGrid(e.target.checked) }), "Show Grid"] })] }), jsx("div", { ref: previewContainerRef, className: "preview-wrapper", onMouseDown: handleMouseDown, children: jsx(IDCardPreview, { template: template, data: sampleData, side: activeSide, showGrid: showGrid, onFieldSelect: handleFieldSelect, selectedFieldId: selectedFieldId, editable: true }) }), jsxs("div", { className: "fields-list", children: [jsxs("h4", { children: ["Fields on ", activeSide, " side:"] }), template.fields
341
441
  .filter((f) => f.side === activeSide)
342
- .map((field) => (jsxs("div", { className: `field-item ${field.id === selectedFieldId ? 'selected' : ''}`, onClick: () => setSelectedFieldId(field.id), children: [jsx("span", { className: "field-type-icon", children: defaultFieldTypes.find((t) => t.type === field.type)?.icon }), jsx("span", { className: "field-key", children: field.fieldKey }), jsx("button", { className: "delete-btn", onClick: (e) => {
442
+ .map((field) => (jsxs("div", { className: `field-item ${field.id === selectedFieldId ? 'selected' : ''}`, onClick: () => setSelectedFieldId(field.id), children: [jsx("span", { className: "field-type-icon", children: defaultFieldTypes.find((t) => t.type === field.type)?.icon }), jsx("span", { className: "field-key", children: field.fieldKey }), jsx("button", { type: "button", className: "delete-btn", onClick: (e) => {
343
443
  e.stopPropagation();
344
444
  removeField(field.id);
345
445
  if (field.id === selectedFieldId)
346
446
  setSelectedFieldId(null);
347
- }, children: "\u00D7" })] }, field.id)))] })] }), jsxs("div", { className: "designer-sidebar right-sidebar", children: [jsx("h3", { children: "Field Properties" }), selectedField ? (jsxs("div", { className: "field-properties", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Field Key" }), jsx("input", { type: "text", value: selectedField.fieldKey, onChange: (e) => handleFieldPropertyUpdate('fieldKey', e.target.value) })] }), selectedField.type !== 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label" }), jsx("input", { type: "text", value: selectedField.label || '', onChange: (e) => handleFieldPropertyUpdate('label', e.target.value) })] })), selectedField.type === 'text' && (jsxs(Fragment, { children: [jsx("button", { className: "btn btn-outline btn-sm", style: { marginBottom: 12 }, onClick: handleDuplicateField, children: "Duplicate Field" }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Label Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Position" }), jsxs("select", { value: selectedField.style?.labelPosition || 'top', onChange: (e) => handleFieldPropertyUpdate('style.labelPosition', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "left", children: "Left (Side)" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Vertical Align" }), jsxs("select", { value: selectedField.style?.labelVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.labelVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Font Size" }), jsx("input", { type: "text", value: selectedField.style?.labelFontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.labelFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Font Weight" }), jsxs("select", { value: selectedField.style?.labelFontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.labelFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Color" }), jsx("input", { type: "color", value: selectedField.style?.labelColor || '#666666', onChange: (e) => handleFieldPropertyUpdate('style.labelColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Text Align" }), jsxs("select", { value: selectedField.style?.labelTextAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.labelTextAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Text Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Font Size" }), jsx("input", { type: "text", value: selectedField.style?.textFontSize || selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.textFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Font Weight" }), jsxs("select", { value: selectedField.style?.textFontWeight || selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.textFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.textColor || selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.textColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textTextAlign || selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textTextAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Line Height" }), jsx("input", { id: "line-height-input", type: "number", min: 0.1, step: 0.1, value: selectedField.style?.lineHeight ? Number(selectedField.style.lineHeight) : 1, onChange: (e) => handleFieldPropertyUpdate('style.lineHeight', e.target.value), style: { width: 80 }, onFocus: () => setLineHeightInputFocused(true), onBlur: () => setLineHeightInputFocused(false) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.textTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.textTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Vertical Align" }), jsxs("select", { value: selectedField.style?.textVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.textVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Container Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Vertical Align (Container)" }), jsxs("select", { value: selectedField.style?.verticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.verticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] })] })), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Static Text" }), jsx("input", { type: "text", value: selectedField.staticText || '', onChange: (e) => handleFieldPropertyUpdate('staticText', e.target.value) })] })), selectedField.type === 'qrcode' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "QR Code Data Fields" }), jsx("small", { className: "hint", children: "Select which fields to encode in the QR code:" }), jsx("div", { style: { marginTop: '8px', maxHeight: '150px', overflowY: 'auto', border: '1px solid #ddd', borderRadius: '4px', padding: '8px' }, children: template.fields
348
- .filter(f => f.type !== 'qrcode' && f.type !== 'label')
447
+ }, children: "\u00D7" })] }, field.id)))] })] }), jsxs("div", { className: "designer-sidebar right-sidebar", children: [jsx("h3", { children: "Field Properties" }), selectedField ? (jsxs("div", { className: "field-properties", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: selectedField.type === 'table' ? 'Data Source Key' : 'Field Key' }), jsx("input", { type: "text", value: selectedField.fieldKey, onChange: (e) => handleFieldPropertyUpdate('fieldKey', e.target.value) })] }), selectedField.type !== 'label' && selectedField.type !== 'table' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label" }), jsx("input", { type: "text", value: selectedField.label || '', onChange: (e) => handleFieldPropertyUpdate('label', e.target.value) })] })), selectedField.type === 'table' && (() => {
448
+ const table = normalizeTableConfig(selectedField.table);
449
+ const sampleRows = coerceTableRows(sampleData[selectedField.fieldKey]);
450
+ const editableRows = Array.from({ length: Math.max(table.rowCount, sampleRows.length) }, (_, index) => sampleRows[index] || {});
451
+ return (jsxs(Fragment, { children: [jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Table Layout" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Rows" }), jsx("input", { type: "number", min: 1, value: table.rowCount, onChange: (e) => handleTableConfigUpdate({
452
+ rowCount: Math.max(1, Number(e.target.value) || 1),
453
+ }) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Columns" }), jsx("input", { type: "number", min: 1, value: table.columns.length, onChange: (e) => handleTableColumnCountUpdate(Number(e.target.value) || 1) })] }), jsxs("div", { className: "property-group checkbox-group", children: [jsx("input", { id: "table-show-header", type: "checkbox", checked: table.showHeader !== false, onChange: (e) => handleTableConfigUpdate({ showHeader: e.target.checked }) }), jsx("label", { htmlFor: "table-show-header", children: "Show Header" })] }), jsxs("div", { className: "property-group checkbox-group", children: [jsx("input", { id: "table-zebra-rows", type: "checkbox", checked: Boolean(table.zebraRows), onChange: (e) => handleTableConfigUpdate({ zebraRows: e.target.checked }) }), jsx("label", { htmlFor: "table-zebra-rows", children: "Zebra Rows" })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Row Height" }), jsx("input", { type: "number", min: 1, placeholder: "Auto", value: table.rowHeight || '', onChange: (e) => handleTableConfigUpdate({
454
+ rowHeight: e.target.value ? Math.max(1, Number(e.target.value)) : undefined,
455
+ }) })] })] }), jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Columns" }), table.columns.map((column, index) => (jsxs("div", { className: "table-column-editor", children: [jsxs("div", { className: "property-group", children: [jsxs("label", { children: ["Header ", index + 1] }), jsx("input", { type: "text", value: column.header, onChange: (e) => handleTableColumnUpdate(index, { header: e.target.value }) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Field Key" }), jsx("input", { type: "text", value: column.fieldKey, onChange: (e) => {
456
+ const nextKey = e.target.value;
457
+ const previousKey = column.fieldKey;
458
+ handleTableColumnUpdate(index, { fieldKey: nextKey });
459
+ if (nextKey && nextKey !== previousKey) {
460
+ setSampleData((prev) => {
461
+ const rows = coerceTableRows(prev[selectedField.fieldKey]);
462
+ return {
463
+ ...prev,
464
+ [selectedField.fieldKey]: rows.map((row) => {
465
+ if (row[nextKey] !== undefined)
466
+ return row;
467
+ return {
468
+ ...row,
469
+ [nextKey]: row[previousKey],
470
+ };
471
+ }),
472
+ };
473
+ });
474
+ }
475
+ } })] }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Width" }), jsx("input", { type: "number", min: 0.1, step: 0.1, value: column.width || 1, onChange: (e) => handleTableColumnUpdate(index, {
476
+ width: Math.max(0.1, Number(e.target.value) || 1),
477
+ }) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Align" }), jsxs("select", { value: column.align || 'left', onChange: (e) => handleTableColumnUpdate(index, {
478
+ align: e.target.value,
479
+ }), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] })] })] }, column.id)))] }), jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Table Style" }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Border Width" }), jsx("input", { type: "number", min: 0, value: table.style?.borderWidth ?? 1, onChange: (e) => handleTableStyleUpdate('borderWidth', Math.max(0, Number(e.target.value) || 0)) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Cell Padding" }), jsx("input", { type: "text", value: table.style?.cellPadding || '2px 4px', onChange: (e) => handleTableStyleUpdate('cellPadding', e.target.value) })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Border Color" }), jsx("input", { type: "color", value: table.style?.borderColor || '#000000', onChange: (e) => handleTableStyleUpdate('borderColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Background" }), jsx("input", { type: "color", value: table.style?.headerBackgroundColor || '#f0f0f0', onChange: (e) => handleTableStyleUpdate('headerBackgroundColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Text Color" }), jsx("input", { type: "color", value: table.style?.headerTextColor || '#000000', onChange: (e) => handleTableStyleUpdate('headerTextColor', e.target.value) })] }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Font Size" }), jsx("input", { type: "text", value: table.style?.headerFontSize || '10px', onChange: (e) => handleTableStyleUpdate('headerFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Weight" }), jsxs("select", { value: table.style?.headerFontWeight || 'bold', onChange: (e) => handleTableStyleUpdate('headerFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" })] })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Text Color" }), jsx("input", { type: "color", value: table.style?.bodyTextColor || '#000000', onChange: (e) => handleTableStyleUpdate('bodyTextColor', e.target.value) })] }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Font Size" }), jsx("input", { type: "text", value: table.style?.bodyFontSize || '10px', onChange: (e) => handleTableStyleUpdate('bodyFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Weight" }), jsxs("select", { value: table.style?.bodyFontWeight || 'normal', onChange: (e) => handleTableStyleUpdate('bodyFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" })] })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Zebra Background" }), jsx("input", { type: "color", value: table.style?.zebraBackgroundColor || '#f7f7f7', onChange: (e) => handleTableStyleUpdate('zebraBackgroundColor', e.target.value) })] })] }), jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Sample Rows" }), jsxs("small", { className: "hint", children: ["Preview data for ", selectedField.fieldKey] }), jsxs("div", { className: "table-sample-grid", style: {
480
+ gridTemplateColumns: `repeat(${table.columns.length}, minmax(90px, 1fr))`,
481
+ }, children: [table.columns.map((column) => (jsx("div", { className: "table-sample-header", children: column.header }, `sample-header-${column.id}`))), editableRows.map((row, rowIndex) => table.columns.map((column) => (jsx("input", { type: "text", value: String(row[column.fieldKey] ?? ''), onChange: (e) => handleTableSampleCellUpdate(selectedField, table, rowIndex, column.fieldKey, e.target.value) }, `sample-${rowIndex}-${column.id}`))))] }), jsx("button", { className: "btn btn-outline btn-sm", type: "button", onClick: () => handleAddTableSampleRow(selectedField), children: "+ Add Sample Row" })] })] }));
482
+ })(), selectedField.type === 'text' && (jsxs(Fragment, { children: [jsx("button", { type: "button", className: "btn btn-outline btn-sm", style: { marginBottom: 12 }, onClick: handleDuplicateField, children: "Duplicate Field" }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Label Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Position" }), jsxs("select", { value: selectedField.style?.labelPosition || 'top', onChange: (e) => handleFieldPropertyUpdate('style.labelPosition', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "left", children: "Left (Side)" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Vertical Align" }), jsxs("select", { value: selectedField.style?.labelVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.labelVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Font Size" }), jsx("input", { type: "text", value: selectedField.style?.labelFontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.labelFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Font Weight" }), jsxs("select", { value: selectedField.style?.labelFontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.labelFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Color" }), jsx("input", { type: "color", value: selectedField.style?.labelColor || '#666666', onChange: (e) => handleFieldPropertyUpdate('style.labelColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Text Align" }), jsxs("select", { value: selectedField.style?.labelTextAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.labelTextAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Text Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Font Size" }), jsx("input", { type: "text", value: selectedField.style?.textFontSize || selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.textFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Font Weight" }), jsxs("select", { value: selectedField.style?.textFontWeight || selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.textFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.textColor || selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.textColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textTextAlign || selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textTextAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Line Height" }), jsx("input", { id: "line-height-input", type: "number", min: 0.1, step: 0.1, value: selectedField.style?.lineHeight ? Number(selectedField.style.lineHeight) : 1, onChange: (e) => handleFieldPropertyUpdate('style.lineHeight', e.target.value), style: { width: 80 }, onFocus: () => setLineHeightInputFocused(true), onBlur: () => setLineHeightInputFocused(false) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.textTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.textTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Vertical Align" }), jsxs("select", { value: selectedField.style?.textVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.textVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Container Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Vertical Align (Container)" }), jsxs("select", { value: selectedField.style?.verticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.verticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] })] })), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Static Text" }), jsx("input", { type: "text", value: selectedField.staticText || '', onChange: (e) => handleFieldPropertyUpdate('staticText', e.target.value) })] })), selectedField.type === 'qrcode' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "QR Code Data Fields" }), jsx("small", { className: "hint", children: "Select which fields to encode in the QR code:" }), jsx("div", { style: { marginTop: '8px', maxHeight: '150px', overflowY: 'auto', border: '1px solid #ddd', borderRadius: '4px', padding: '8px' }, children: template.fields
483
+ .filter(f => f.type !== 'qrcode' && f.type !== 'label' && f.type !== 'table')
349
484
  .map((field) => (jsxs("label", { style: { display: 'block', marginBottom: '6px', cursor: 'pointer' }, children: [jsx("input", { type: "checkbox", checked: selectedField.qrFields?.includes(field.fieldKey) || false, onChange: (e) => {
350
485
  const currentFields = selectedField.qrFields || [];
351
486
  const newFields = e.target.checked
352
487
  ? [...currentFields, field.fieldKey]
353
488
  : currentFields.filter(f => f !== field.fieldKey);
354
489
  handleFieldPropertyUpdate('qrFields', newFields);
355
- }, style: { marginRight: '6px' } }), field.label || field.fieldKey] }, field.fieldKey))) })] })), selectedField.type !== 'text' && jsxs(Fragment, { children: [jsx("h4", { children: "Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Size" }), jsx("input", { type: "text", value: selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.fontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Weight" }), jsxs("select", { value: selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.fontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.color', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }))] }), selectedField.type === 'qrcode' ? (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsx("div", { style: {
490
+ }, style: { marginRight: '6px' } }), field.label || field.fieldKey] }, field.fieldKey))) })] })), selectedField.type !== 'text' && selectedField.type !== 'table' && jsxs(Fragment, { children: [jsx("h4", { children: "Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Size" }), jsx("input", { type: "text", value: selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.fontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Weight" }), jsxs("select", { value: selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.fontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.color', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }))] }), selectedField.type === 'qrcode' ? (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsx("div", { style: {
356
491
  padding: '8px 10px',
357
492
  border: '1px solid #ddd',
358
493
  borderRadius: '4px',
359
494
  backgroundColor: '#f8f8f8',
360
495
  color: '#666',
361
- }, children: "Transparent" })] })) : (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [jsx("input", { type: "checkbox", id: "transparent-bg-checkbox", checked: selectedField.style?.backgroundColor === 'transparent', onChange: e => handleFieldPropertyUpdate('style.backgroundColor', e.target.checked ? 'transparent' : '#ffffff') }), jsx("label", { htmlFor: "transparent-bg-checkbox", style: { margin: 0, fontWeight: 400, fontSize: '0.95em' }, children: "Transparent" }), selectedField.style?.backgroundColor !== 'transparent' && (jsx("input", { type: "color", value: selectedField.style?.backgroundColor || '#ffffff', onChange: e => handleFieldPropertyUpdate('style.backgroundColor', e.target.value) }))] })] })), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Side" }), jsxs("select", { value: selectedField.side, onChange: (e) => handleFieldPropertyUpdate('side', e.target.value), children: [jsx("option", { value: "front", children: "Front" }), template.sides === 'double' && jsx("option", { value: "back", children: "Back" })] })] }), jsx("button", { className: "btn btn-danger", onClick: handleDeleteField, children: "Delete Field" })] })) : (jsx("p", { className: "no-selection", children: "Select a field to edit its properties" })), jsx("h3", { children: "Sample Data" }), jsxs("div", { className: "sample-data", children: [jsx("p", { className: "hint", children: "Edit sample data to preview:" }), Object.entries(sampleData).map(([key, value]) => (jsxs("div", { className: "property-group", children: [jsx("label", { children: key }), jsx("input", { type: "text", value: String(value), onChange: (e) => setSampleData((prev) => ({ ...prev, [key]: e.target.value })) })] }, key))), jsx("button", { className: "btn btn-outline btn-sm", onClick: () => setSampleData((prev) => ({ ...prev, [`custom_${Date.now()}`]: '' })), children: "+ Add Field" })] }), validationErrors.length > 0 && (jsxs("div", { className: "validation-errors", children: [jsx("h4", { children: "\u26A0\uFE0F Validation Issues" }), jsx("ul", { children: validationErrors.map((error, index) => (jsx("li", { children: error }, index))) })] }))] })] })] }));
496
+ }, children: "Transparent" })] })) : (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [jsx("input", { type: "checkbox", id: "transparent-bg-checkbox", checked: selectedField.style?.backgroundColor === 'transparent', onChange: e => handleFieldPropertyUpdate('style.backgroundColor', e.target.checked ? 'transparent' : '#ffffff') }), jsx("label", { htmlFor: "transparent-bg-checkbox", style: { margin: 0, fontWeight: 400, fontSize: '0.95em' }, children: "Transparent" }), selectedField.style?.backgroundColor !== 'transparent' && (jsx("input", { type: "color", value: selectedField.style?.backgroundColor || '#ffffff', onChange: e => handleFieldPropertyUpdate('style.backgroundColor', e.target.value) }))] })] })), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Side" }), jsxs("select", { value: selectedField.side, onChange: (e) => handleFieldPropertyUpdate('side', e.target.value), children: [jsx("option", { value: "front", children: "Front" }), template.sides === 'double' && jsx("option", { value: "back", children: "Back" })] })] }), jsx("button", { type: "button", className: "btn btn-danger", onClick: handleDeleteField, children: "Delete Field" })] })) : (jsx("p", { className: "no-selection", children: "Select a field to edit its properties" })), jsx("h3", { children: "Sample Data" }), jsxs("div", { className: "sample-data", children: [jsx("p", { className: "hint", children: "Edit sample data to preview:" }), Object.entries(sampleData)
497
+ .filter(([, value]) => !Array.isArray(value))
498
+ .map(([key, value]) => (jsxs("div", { className: "property-group", children: [jsx("label", { children: key }), jsx("input", { type: "text", value: String(value), onChange: (e) => setSampleData((prev) => ({ ...prev, [key]: e.target.value })) })] }, key))), jsx("button", { type: "button", className: "btn btn-outline btn-sm", onClick: () => setSampleData((prev) => ({ ...prev, [`custom_${Date.now()}`]: '' })), children: "+ Add Field" })] }), validationErrors.length > 0 && (jsxs("div", { className: "validation-errors", children: [jsx("h4", { children: "\u26A0\uFE0F Validation Issues" }), jsx("ul", { children: validationErrors.map((error, index) => (jsx("li", { children: error }, index))) })] }))] })] })] }));
362
499
  };
363
500
 
364
501
  export { IDCardDesigner };