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.
- package/README.md +33 -12
- package/dist/cjs/components/IDCardDesigner.js +147 -10
- package/dist/cjs/components/IDCardDesigner.js.map +1 -1
- package/dist/cjs/components/IDCardGenerator.js +8 -3
- package/dist/cjs/components/IDCardGenerator.js.map +1 -1
- package/dist/cjs/components/IDCardPreview.js +3 -2
- package/dist/cjs/components/IDCardPreview.js.map +1 -1
- package/dist/cjs/components/IDCardTable.js +74 -0
- package/dist/cjs/components/IDCardTable.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/storage/templateStorage.js +2 -2
- package/dist/cjs/storage/templateStorage.js.map +1 -1
- package/dist/cjs/styles.css +1 -1
- package/dist/cjs/utils/styleUtils.js +3 -0
- package/dist/cjs/utils/styleUtils.js.map +1 -1
- package/dist/cjs/utils/tableUtils.js +106 -0
- package/dist/cjs/utils/tableUtils.js.map +1 -0
- package/dist/cjs/utils/validators.js +33 -10
- package/dist/cjs/utils/validators.js.map +1 -1
- package/dist/esm/components/IDCardDesigner.js +147 -10
- package/dist/esm/components/IDCardDesigner.js.map +1 -1
- package/dist/esm/components/IDCardGenerator.js +8 -3
- package/dist/esm/components/IDCardGenerator.js.map +1 -1
- package/dist/esm/components/IDCardPreview.js +3 -2
- package/dist/esm/components/IDCardPreview.js.map +1 -1
- package/dist/esm/components/IDCardTable.js +72 -0
- package/dist/esm/components/IDCardTable.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/storage/templateStorage.js +2 -2
- package/dist/esm/storage/templateStorage.js.map +1 -1
- package/dist/esm/styles.css +1 -1
- package/dist/esm/utils/styleUtils.js +3 -0
- package/dist/esm/utils/styleUtils.js.map +1 -1
- package/dist/esm/utils/tableUtils.js +96 -0
- package/dist/esm/utils/tableUtils.js.map +1 -0
- package/dist/esm/utils/validators.js +33 -10
- package/dist/esm/utils/validators.js.map +1 -1
- package/dist/index.d.ts +43 -4
- package/dist/types/components/IDCardTable.d.ts +8 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/types/index.d.ts +37 -2
- package/dist/types/utils/tableUtils.d.ts +10 -0
- 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
|
-
//
|
|
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":";;AAOA;;;;;AAKG;AACG,SAAU,gBAAgB,CAAC,QAAwB,EAAA;IACvD,MAAM,MAAM,GAAsB,EAAE;;AAGpC,IAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;AAChB,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;IAClE;AAEA,IAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;AAClB,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACtE;;AAGA,IAAA,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE;AACtB,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACtE;SAAO;QACL,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,EAAE;AAChC,YAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;QAClF;QACA,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,EAAE;AACjC,YAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC;QACpF;IACF;;AAGA,IAAA,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,QAAQ,CAAC,KAAK,KAAK,QAAQ,EAAE;AAC/D,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC;IACtF;AAEA,IAAA,IAAI,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,EAAE;QAC9D,MAAM,CAAC,IAAI,CAAC;AACV,YAAA,KAAK,EAAE,kBAAkB;AACzB,YAAA,OAAO,EAAE,oDAAoD;AAC9D,SAAA,CAAC;IACJ;;AAGA,IAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE;AACpD,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC;IAC7E;SAAO;QACL,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,KAAI;YACvC,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC;AAC/C,YAAA,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC;AAC7B,QAAA,CAAC,CAAC;IACJ;;AAGA,IAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE;IACxD,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC;AACnF,IAAA,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;QAC3B,MAAM,CAAC,IAAI,CAAC;AACV,YAAA,KAAK,EAAE,QAAQ;AACf,YAAA,OAAO,EAAE,CAAA,qBAAA,EAAwB,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAE;AACzE,SAAA,CAAC;IACJ;AAEA,IAAA,OAAO,MAAM;AACf;AAEA;;;;;AAKG;AACG,SAAU,aAAa,CAAC,KAAmB,EAAE,KAAa,EAAA;IAC9D,MAAM,MAAM,GAAsB,EAAE;AACpC,IAAA,MAAM,MAAM,GAAG,CAAA,OAAA,EAAU,KAAK,GAAG;AAEjC,IAAA,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE;AACb,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,GAAA,CAAK,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC;IACzE;AAEA,IAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;AACnB,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,SAAA,CAAW,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IAChF;AAEA,IAAA,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;AACf,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,KAAA,CAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IAC7E;AAEA,IAAA,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;AACf,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,KAAA,CAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IAC7E;AAEA,IAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;AACnB,QAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,SAAA,CAAW,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC;IACrF;SAAO;QACL,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE;AACxB,YAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,WAAA,CAAa,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;QAC1F;QACA,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE;AACxB,YAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,WAAA,CAAa,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;QAC1F;QACA,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,EAAE;AAC7B,YAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,eAAA,CAAiB,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;QACvF;QACA,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,EAAE;AAC9B,YAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,gBAAA,CAAkB,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;QACzF;IACF;IAIA,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE;QAC/E,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,SAAA,CAAW;AAC3B,YAAA,OAAO,EAAE,gDAAgD;AAC1D,SAAA,CAAC;IACJ;AAEA,IAAA,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;QAC/D,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,CAAA,EAAG,MAAM,CAAA,WAAA,CAAa;AAC7B,YAAA,OAAO,EAAE,kCAAkC;AAC5C,SAAA,CAAC;IACJ;AAEA,IAAA,OAAO,MAAM;AACf;AAEA;;AAEG;AACG,SAAU,gBAAgB,CAAC,IAAc,EAAA;;AAQ7C,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE;QACtB,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;IACzF;AACA,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE;QACtB,OAAO,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;IAC7F;AACA,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE;QACtB,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE;IACrF;AAEA,IAAA,OAAO,IAAI;AACb;AAEA;;AAEG;AACG,SAAU,eAAe,CAC7B,KAAmB,EACnB,QAAkB,EAAA;AAElB,IAAA,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ;IAC9C,QACE,CAAC,IAAI,CAAC;AACN,QAAA,CAAC,IAAI,CAAC;AACN,QAAA,CAAC,GAAG,KAAK,IAAI,QAAQ,CAAC,KAAK;AAC3B,QAAA,CAAC,GAAG,MAAM,IAAI,QAAQ,CAAC,MAAM;AAEjC;;;;;;;"}
|
|
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 =
|
|
131
|
-
const baseHeight =
|
|
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:
|
|
348
|
-
|
|
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)
|
|
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 };
|