hazo_config 2.5.0 → 2.6.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/CHANGE_LOG.md +47 -0
- package/dist/components/app_config.d.ts.map +1 -1
- package/dist/components/app_config.js +14 -13
- package/dist/components/app_config_list_editor/app_config_list_editor.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/app_config_list_editor.js +8 -4
- package/dist/components/app_config_list_editor/components/bulk_edit_modal.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/bulk_edit_modal.js +5 -4
- package/dist/components/app_config_list_editor/components/color_swatch_picker.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/color_swatch_picker.js +2 -1
- package/dist/components/app_config_list_editor/components/delete_dialog.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/delete_dialog.js +2 -1
- package/dist/components/app_config_list_editor/components/edit_modal.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/edit_modal.js +14 -7
- package/dist/components/app_config_list_editor/components/empty_state.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/empty_state.js +2 -1
- package/dist/components/app_config_list_editor/components/import_export_buttons.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/import_export_buttons.js +8 -7
- package/dist/components/app_config_list_editor/components/list_item_row.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/list_item_row.js +4 -3
- package/dist/components/app_config_list_editor/components/save_status_indicator.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/save_status_indicator.js +2 -1
- package/dist/components/app_config_list_editor/components/search_bar.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/search_bar.js +2 -1
- package/dist/components/config_editor.d.ts.map +1 -1
- package/dist/components/config_editor.js +5 -4
- package/dist/components/config_viewer.d.ts.map +1 -1
- package/dist/components/config_viewer.js +3 -2
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +3 -0
- package/dist/lib/strings.d.ts +2 -0
- package/dist/lib/strings.d.ts.map +1 -0
- package/dist/lib/strings.js +18 -0
- package/dist/locales/en.json +104 -0
- package/package.json +12 -6
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.6.0 — 2026-09-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
Full adoption of the canonical `hazo_core/i18n` bridge (`createPackageT`) for
|
|
8
|
+
this package's own baked-in UI strings — hazo_config previously had no i18n
|
|
9
|
+
bridge at all.
|
|
10
|
+
|
|
11
|
+
- `src/locales/en.json` — a new flat, literal-dotted fallback catalog (102
|
|
12
|
+
keys) covering every `'use client'` component's user-facing text: the
|
|
13
|
+
`ConfigEditor`/`ConfigViewer` screens, `AppConfig` (+ its add-key/add-section
|
|
14
|
+
`KeyDialog`), and the whole `AppConfigListEditor` family (`SearchBar`,
|
|
15
|
+
`EmptyState`, `ImportExportButtons`, `ListItemRow`, `EditModal`,
|
|
16
|
+
`BulkEditModal`, `DeleteDialog`, `ColorSwatchPicker`,
|
|
17
|
+
`SaveStatusIndicator`). Namespaced by component (`config_editor.*`,
|
|
18
|
+
`config_viewer.*`, `app_config.*`, `list_editor.*`, `bulk_edit.*`,
|
|
19
|
+
`edit_modal.*`, `search.*`, `empty_state.*`, `import_export.*`,
|
|
20
|
+
`list_item.*`, `save_status.*`, `color_swatch.*`), with a small `common.*`
|
|
21
|
+
namespace (`cancel`, `delete`, `select_placeholder`, `hide_value`,
|
|
22
|
+
`show_value`) for strings that were byte-identical across components.
|
|
23
|
+
`{{var}}` mustache interpolation used for all dynamic copy (item counts,
|
|
24
|
+
labels, key names).
|
|
25
|
+
- `src/lib/strings.ts` — `export const t = createPackageT('hazo_config', en)`,
|
|
26
|
+
re-exported from `hazo_config/lib`. A plain JSON import (no
|
|
27
|
+
`with { type: 'json' }` attribute) is used since every consuming component
|
|
28
|
+
is client-bundled and this package's tsconfig already has
|
|
29
|
+
`moduleResolution: "bundler"` + `resolveJsonModule: true`.
|
|
30
|
+
- Every hardcoded UI string literal across the components above now routes
|
|
31
|
+
through `t()` — labels, buttons, aria-labels, placeholders, validation
|
|
32
|
+
messages, and toast/status text. Original estimate was ~30 strings; actual
|
|
33
|
+
extraction came to 102 catalog keys (the `AppConfigListEditor` family alone
|
|
34
|
+
accounts for over half).
|
|
35
|
+
- `hazo_ui/test-harness` autotest group `hazo_config_i18n` (new
|
|
36
|
+
`test-app/app/autotest/` route + sidebar entry) — catalog-loads,
|
|
37
|
+
all-sampled-keys-resolve, interpolation, and coverage-threshold cases,
|
|
38
|
+
matching the pattern used by `hazo_ui`'s own i18n autotest scenarios.
|
|
39
|
+
- `hazo_i18n` added as an **optional** peer dependency (`^0.5.0`); `hazo_core`
|
|
40
|
+
peer range bumped `^2.3.0` → `^2.4.0` (first version shipping
|
|
41
|
+
`hazo_core/i18n`).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
`tsconfig.build.json`'s `moduleResolution` was hardcoded to the classic
|
|
46
|
+
`"node"` resolver, which cannot follow a `package.json` `exports` subpath
|
|
47
|
+
(e.g. `hazo_core/i18n`) — TS2307. Switched to `"bundler"`, matching the
|
|
48
|
+
package's own `tsconfig.json`.
|
|
49
|
+
|
|
3
50
|
## 2.5.0 — 2026-09-06
|
|
4
51
|
|
|
5
52
|
### Added
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_config.d.ts","sourceRoot":"","sources":["../../src/components/app_config.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"app_config.d.ts","sourceRoot":"","sources":["../../src/components/app_config.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAM7D,OAAO,KAAK,EAAE,cAAc,EAA6B,MAAM,4BAA4B,CAAA;AA0P3F;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA6a9C,CAAA"}
|
|
@@ -7,6 +7,7 @@ import { cn } from 'hazo_ui';
|
|
|
7
7
|
import { RefreshCw, Save, Trash2, Eye, EyeOff, Plus, X } from 'lucide-react';
|
|
8
8
|
import { useAppConfig } from './use_app_config.js';
|
|
9
9
|
import { is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
|
|
10
|
+
import { t } from '../lib/strings.js';
|
|
10
11
|
const JsonEditor = ({ value, onChange, onValidationChange, placeholder, className }) => {
|
|
11
12
|
const [text_value, set_text_value] = useState(() => JSON.stringify(value, null, 2));
|
|
12
13
|
const [validation_error, set_validation_error] = useState();
|
|
@@ -20,7 +21,7 @@ const JsonEditor = ({ value, onChange, onValidationChange, placeholder, classNam
|
|
|
20
21
|
onValidationChange?.(true);
|
|
21
22
|
}
|
|
22
23
|
catch (err) {
|
|
23
|
-
const error_msg = err instanceof Error ? err.message : '
|
|
24
|
+
const error_msg = err instanceof Error ? err.message : t('app_config.invalid_json');
|
|
24
25
|
set_validation_error(error_msg);
|
|
25
26
|
onValidationChange?.(false, error_msg);
|
|
26
27
|
}
|
|
@@ -67,7 +68,7 @@ const KeyDialog = ({ mode, section_name, on_close, on_save }) => {
|
|
|
67
68
|
: new_key && new_value && is_json_valid;
|
|
68
69
|
if (!mode)
|
|
69
70
|
return null;
|
|
70
|
-
return (_jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50", children: _jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4", children: [_jsxs("div", { className: "flex items-center justify-between p-6 border-b", children: [_jsx("h3", { className: "text-lg font-semibold", children: mode === 'add-section' ? '
|
|
71
|
+
return (_jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50", children: _jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4", children: [_jsxs("div", { className: "flex items-center justify-between p-6 border-b", children: [_jsx("h3", { className: "text-lg font-semibold", children: mode === 'add-section' ? t('app_config.dialog_title_add_section') : t('app_config.dialog_title_add_key') }), _jsx("button", { onClick: on_close, className: "text-muted-foreground hover:text-foreground", type: "button", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsxs("div", { className: "p-6 space-y-4", children: [mode === 'add-section' && (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.section_name_label') }), _jsx("input", { type: "text", placeholder: t('app_config.section_name_placeholder'), value: new_section, onChange: (e) => set_new_section(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", autoFocus: true })] })), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.key_name_label') }), _jsx("input", { type: "text", placeholder: t('app_config.key_name_placeholder'), value: new_key, onChange: (e) => set_new_key(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", autoFocus: mode !== 'add-section' })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.type_label') }), _jsxs("div", { className: "flex gap-4", children: [_jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [_jsx("input", { type: "radio", name: "type", value: "general", checked: new_type === 'general', onChange: (e) => set_new_type(e.target.value), className: "w-4 h-4 text-primary" }), _jsx("span", { className: "text-sm", children: t('app_config.type_general') })] }), _jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [_jsx("input", { type: "radio", name: "type", value: "json", checked: new_type === 'json', onChange: (e) => set_new_type(e.target.value), className: "w-4 h-4 text-primary" }), _jsx("span", { className: "text-sm", children: t('app_config.type_json') })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.value_label') }), new_type === 'json' ? (_jsx(JsonEditor, { value: new_value ? (() => {
|
|
71
72
|
try {
|
|
72
73
|
return JSON.parse(new_value);
|
|
73
74
|
}
|
|
@@ -76,9 +77,9 @@ const KeyDialog = ({ mode, section_name, on_close, on_save }) => {
|
|
|
76
77
|
}
|
|
77
78
|
})() : {}, onChange: (val) => set_new_value(JSON.stringify(val, null, 2)), onValidationChange: (isValid) => {
|
|
78
79
|
set_is_json_valid(isValid);
|
|
79
|
-
}, placeholder: '{"key": "value"}' })) : (_jsx("input", { type: "text", placeholder:
|
|
80
|
+
}, placeholder: '{"key": "value"}' })) : (_jsx("input", { type: "text", placeholder: t('app_config.value_placeholder'), value: new_value, onChange: (e) => set_new_value(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary" }))] })] }), _jsxs("div", { className: "flex items-center justify-end gap-3 p-6 border-t bg-muted/30", children: [_jsx("button", { onClick: on_close, className: "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground", type: "button", children: t('common.cancel') }), _jsx("button", { onClick: handle_save, disabled: !can_save || is_saving, className: cn('px-4 py-2 text-sm font-medium rounded-md', can_save && !is_saving
|
|
80
81
|
? 'bg-primary text-primary-foreground hover:bg-primary/90'
|
|
81
|
-
: 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", children: is_saving ? '
|
|
82
|
+
: 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", children: is_saving ? t('app_config.saving') : mode === 'add-section' ? t('app_config.create_section') : t('app_config.add_key') })] })] }) }));
|
|
82
83
|
};
|
|
83
84
|
/**
|
|
84
85
|
* AppConfig component
|
|
@@ -87,7 +88,7 @@ const KeyDialog = ({ mode, section_name, on_close, on_save }) => {
|
|
|
87
88
|
* @param props - Component props
|
|
88
89
|
* @returns React component
|
|
89
90
|
*/
|
|
90
|
-
export const AppConfig = ({ context, fetch_config, save_config, delete_config, className, on_update, on_error, sensitive_fields, sensitive_patterns = DEFAULT_SENSITIVE_PATTERNS, disable_auto_mask = false, title = '
|
|
91
|
+
export const AppConfig = ({ context, fetch_config, save_config, delete_config, className, on_update, on_error, sensitive_fields, sensitive_patterns = DEFAULT_SENSITIVE_PATTERNS, disable_auto_mask = false, title = t('app_config.title_default'), }) => {
|
|
91
92
|
const { sections, is_loading, error, reload, set_value, delete_value, } = useAppConfig(context, fetch_config, save_config, delete_config);
|
|
92
93
|
const [selected_section, set_selected_section] = useState(null);
|
|
93
94
|
const [editing_fields, set_editing_fields] = useState(new Map());
|
|
@@ -217,7 +218,7 @@ export const AppConfig = ({ context, fetch_config, save_config, delete_config, c
|
|
|
217
218
|
* Handle delete key
|
|
218
219
|
*/
|
|
219
220
|
const handle_delete_key = useCallback(async (section, key) => {
|
|
220
|
-
if (!window.confirm(
|
|
221
|
+
if (!window.confirm(t('app_config.delete_key_confirm', { key })))
|
|
221
222
|
return;
|
|
222
223
|
try {
|
|
223
224
|
await delete_value(section, key);
|
|
@@ -270,13 +271,13 @@ export const AppConfig = ({ context, fetch_config, save_config, delete_config, c
|
|
|
270
271
|
return selected_section ? sections[selected_section] : null;
|
|
271
272
|
}, [selected_section, sections]);
|
|
272
273
|
if (is_loading) {
|
|
273
|
-
return (_jsx("div", { className: cn('cls_app_config flex items-center justify-center p-8', className), children: _jsx("div", { className: "text-muted-foreground", children:
|
|
274
|
+
return (_jsx("div", { className: cn('cls_app_config flex items-center justify-center p-8', className), children: _jsx("div", { className: "text-muted-foreground", children: t('app_config.loading') }) }));
|
|
274
275
|
}
|
|
275
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn('cls_app_config space-y-6', className), children: [_jsxs("div", { className: "cls_app_config_header flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold", children: title }), has_unsaved_changes && (_jsx("p", { className: "text-sm text-amber-600 mt-1", children:
|
|
276
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn('cls_app_config space-y-6', className), children: [_jsxs("div", { className: "cls_app_config_header flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold", children: title }), has_unsaved_changes && (_jsx("p", { className: "text-sm text-amber-600 mt-1", children: t('app_config.unsaved_changes') }))] }), _jsxs("div", { className: "flex gap-3", children: [_jsxs("button", { onClick: handle_refresh, className: "px-4 py-2 border border-input rounded-md hover:bg-accent hover:text-accent-foreground flex items-center gap-2 transition-colors", type: "button", disabled: is_saving, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), t('app_config.refresh')] }), _jsxs("button", { onClick: handle_save, className: cn('px-4 py-2 rounded-md flex items-center gap-2 transition-colors', has_unsaved_changes
|
|
276
277
|
? 'bg-primary text-primary-foreground hover:bg-primary/90'
|
|
277
|
-
: 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", disabled: !has_unsaved_changes || is_saving, children: [_jsx(Save, { className: "h-4 w-4" }), is_saving ? '
|
|
278
|
+
: 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", disabled: !has_unsaved_changes || is_saving, children: [_jsx(Save, { className: "h-4 w-4" }), is_saving ? t('app_config.saving') : t('app_config.save')] })] })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-4 gap-6", children: [_jsx("div", { className: "lg:col-span-1", children: _jsxs("div", { className: "bg-card border border-border rounded-lg overflow-hidden", children: [_jsx("div", { className: "p-4 border-b bg-muted/30", children: _jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "font-semibold", children: t('app_config.sections_heading') }), _jsx("button", { onClick: () => set_dialog_mode('add-section'), className: "text-primary hover:text-primary/80 transition-colors", type: "button", children: _jsx(Plus, { className: "h-5 w-5" }) })] }) }), _jsx("div", { className: "p-2", children: section_list.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground text-center py-8 px-4", children: t('app_config.no_sections') })) : (_jsx("div", { className: "space-y-1", children: section_list.map((section_name) => (_jsx("button", { onClick: () => set_selected_section(section_name), className: cn('w-full text-left px-3 py-2 rounded-md text-sm font-medium transition-colors', selected_section === section_name
|
|
278
279
|
? 'bg-primary text-primary-foreground'
|
|
279
|
-
: 'hover:bg-muted'), type: "button", children: section_name }, section_name))) })) })] }) }), _jsx("div", { className: "lg:col-span-3", children: selected_section && current_section_data ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-xl font-semibold", children: selected_section }), _jsxs("button", { onClick: () => set_dialog_mode('add'), className: "px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 flex items-center gap-2 transition-colors", type: "button", children: [_jsx(Plus, { className: "h-4 w-4" }),
|
|
280
|
+
: 'hover:bg-muted'), type: "button", children: section_name }, section_name))) })) })] }) }), _jsx("div", { className: "lg:col-span-3", children: selected_section && current_section_data ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-xl font-semibold", children: selected_section }), _jsxs("button", { onClick: () => set_dialog_mode('add'), className: "px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 flex items-center gap-2 transition-colors", type: "button", children: [_jsx(Plus, { className: "h-4 w-4" }), t('app_config.add_key')] })] }), _jsxs("div", { className: "space-y-3", children: [Object.entries(current_section_data).map(([key, item]) => {
|
|
280
281
|
const is_sensitive = should_mask_field(key);
|
|
281
282
|
const show_value = !is_sensitive || is_revealed(selected_section, key);
|
|
282
283
|
const display_value = get_field_value(selected_section, key, item);
|
|
@@ -285,7 +286,7 @@ export const AppConfig = ({ context, fetch_config, save_config, delete_config, c
|
|
|
285
286
|
: 'bg-gray-100 text-gray-700'), children: item.config_type })] }) }), _jsxs("div", { className: "flex items-center gap-2", children: [is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(selected_section, key), className: "text-muted-foreground hover:text-foreground transition-colors", type: "button", children: show_value ? _jsx(EyeOff, { className: "h-4 w-4" }) : _jsx(Eye, { className: "h-4 w-4" }) })), _jsx("button", { onClick: () => handle_delete_key(selected_section, key), className: "text-red-500 hover:text-red-700 transition-colors", type: "button", children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }), item.config_type === 'json' ? (_jsx(JsonEditor, { value: typeof display_value === 'object' ? display_value : {}, onChange: (val) => handle_field_change(selected_section, key, val), className: "bg-muted/30" })) : (_jsx("input", { type: is_sensitive && !show_value ? 'password' : 'text', value: is_sensitive && !show_value
|
|
286
287
|
? mask_value(String(display_value))
|
|
287
288
|
: String(display_value), onChange: (e) => handle_field_change(selected_section, key, e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", readOnly: is_sensitive && !show_value }))] }, key));
|
|
288
|
-
}), Object.keys(current_section_data).length === 0 && (_jsx("div", { className: "text-center py-12 text-muted-foreground", children:
|
|
289
|
-
? '
|
|
290
|
-
: '
|
|
289
|
+
}), Object.keys(current_section_data).length === 0 && (_jsx("div", { className: "text-center py-12 text-muted-foreground", children: t('app_config.no_keys') }))] })] })) : (_jsx("div", { className: "flex items-center justify-center h-64 bg-muted/30 border border-dashed border-border rounded-lg", children: _jsx("div", { className: "text-center text-muted-foreground", children: section_list.length > 0
|
|
290
|
+
? t('app_config.select_section_prompt')
|
|
291
|
+
: t('app_config.create_first_section_prompt') }) })) })] })] }), _jsx(KeyDialog, { mode: dialog_mode, section_name: selected_section || undefined, on_close: () => set_dialog_mode(null), on_save: handle_dialog_save })] }));
|
|
291
292
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_config_list_editor.d.ts","sourceRoot":"","sources":["../../../src/components/app_config_list_editor/app_config_list_editor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app_config_list_editor.d.ts","sourceRoot":"","sources":["../../../src/components/app_config_list_editor/app_config_list_editor.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,wBAAwB,EAAqC,MAAM,YAAY,CAAA;AAS7F;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACrE,KAAK,EACL,eAAe,EACf,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,KAAK,EACL,WAAW,EACX,aAAqB,EACrB,gBAAoB,EACpB,WAAW,EACX,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,wBAAgC,EAChC,SAAS,EACT,WAAoB,EACpB,oBAAoB,EAEpB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,cAAc,EAEd,cAAc,EACd,cAAc,EACd,cAAc,EACd,SAAS,GACV,EAAE,wBAAwB,CAAC,CAAC,CAAC,+BA0c7B"}
|
|
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
import { useState, useCallback, useMemo } from 'react';
|
|
6
6
|
import { Plus, Trash2 } from 'lucide-react';
|
|
7
7
|
import { cn, HazoUiConfirmDialog } from 'hazo_ui';
|
|
8
|
+
import { t } from '../../lib/strings.js';
|
|
8
9
|
import { ListItemRow } from './components/list_item_row.js';
|
|
9
10
|
import { EditModal } from './components/edit_modal.js';
|
|
10
11
|
import { SearchBar } from './components/search_bar.js';
|
|
@@ -248,9 +249,12 @@ on_item_create, on_item_update, on_item_delete, on_reload, }) {
|
|
|
248
249
|
return (_jsxs("div", { className: cn('cls_list_editor', className), children: [(title || description) && (_jsxs("div", { className: "cls_list_editor_header mb-6", children: [title && (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h2", { className: "text-2xl font-bold text-gray-900", children: title }), _jsx(SaveStatusIndicator, { status: save_status })] })), description && (_jsx("p", { className: "text-sm text-gray-500 mt-1", children: description }))] })), _jsxs("div", { className: "cls_list_editor_section_bar flex items-center justify-between mb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [enable_selection && filtered_items.length > 0 && (_jsx("input", { type: "checkbox", checked: all_filtered_selected, ref: (el) => {
|
|
249
250
|
if (el)
|
|
250
251
|
el.indeterminate = !all_filtered_selected && some_filtered_selected;
|
|
251
|
-
}, onChange: toggle_select_all, className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label":
|
|
252
|
+
}, onChange: toggle_select_all, className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": t('list_editor.select_all_aria') })), _jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-gray-500", children: section_label })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(ImportExportButtons, { items: items, on_items_change: on_items_change, columns: columns, id_field: id_field, title: title, max_items: max_items }), _jsxs("button", { type: "button", onClick: handle_add, disabled: is_max_reached, className: cn('inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full transition-colors', is_max_reached
|
|
252
253
|
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
253
|
-
: 'bg-violet-600 hover:bg-violet-700 text-white'), title: is_max_reached ?
|
|
254
|
-
handle_cancel_delete(); }, title:
|
|
255
|
-
set_bulk_delete_open(false); }, title:
|
|
254
|
+
: 'bg-violet-600 hover:bg-violet-700 text-white'), title: is_max_reached ? t('list_editor.max_items_title', { max: max_items ?? 0 }) : undefined, children: [_jsx(Plus, { className: "w-4 h-4" }), t('list_editor.add')] })] })] }), enable_selection && selection_count > 0 && (_jsxs("div", { className: "cls_bulk_action_bar flex items-center gap-3 mb-3 px-4 py-2.5 bg-violet-50 border border-violet-200 rounded-xl", children: [_jsx("span", { className: "text-sm font-medium text-violet-800", children: t('list_editor.selected_count', { count: selection_count }) }), _jsx("button", { type: "button", onClick: clear_selection, className: "text-xs text-violet-600 hover:text-violet-800 underline", children: t('list_editor.clear') }), _jsx("div", { className: "flex-1" }), bulk_edit_fields && bulk_edit_fields.length > 0 && on_bulk_update && (_jsx("button", { type: "button", onClick: () => set_bulk_edit_open(true), className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full bg-violet-600 hover:bg-violet-700 text-white transition-colors", children: t('list_editor.edit_selected') })), on_bulk_delete && (_jsxs("button", { type: "button", onClick: handle_bulk_delete, className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full bg-red-600 hover:bg-red-700 text-white transition-colors", children: [_jsx(Trash2, { className: "w-3.5 h-3.5" }), t('list_editor.delete_selected')] }))] })), show_search && (_jsx("div", { className: "mb-3", children: _jsx(SearchBar, { value: search_term, on_change: set_search_term, item_count: filtered_items.length, item_label: item_label }) })), _jsx("div", { className: "cls_list_editor_card rounded-xl border border-gray-200 bg-white overflow-hidden", children: items.length === 0 ? (_jsx(EmptyState, { item_label: item_label, on_add: handle_add })) : filtered_items.length === 0 ? (_jsx("div", { className: "py-8 text-center text-sm text-gray-500", children: t('list_editor.no_items_matching', { item_label, term: search_term }) })) : (_jsx("div", { className: "divide-y divide-gray-100", children: filtered_items.map((item, index) => (_jsx(ListItemRow, { item: item, index: index, columns: columns, render_item: render_item, render_item_indicator: render_item_indicator, on_edit: () => handle_edit(item), on_delete: () => handle_delete_request(item), show_checkbox: enable_selection, is_selected: selected_ids.has(String(item[id_field])), on_toggle_select: () => toggle_row(String(item[id_field])) }, String(item[id_field])))) })) }), is_max_reached && (_jsx("p", { className: "text-xs text-gray-400 mt-2", children: t('list_editor.max_reached_note', { max: max_items ?? 0, item_label }) })), edit_state && (_jsx(EditModal, { open: true, mode: edit_state.mode, item: edit_state.item, columns: columns, id_field: id_field, auto_id_from: auto_id_from, id_editable_after_create: id_editable_after_create, existing_ids: existing_ids, item_type_label: item_type_label, render_preview: render_preview, on_save: handle_save, on_cancel: handle_cancel_edit, max_width_class: edit_modal_max_width, is_saving: is_saving })), delete_state && (_jsx(HazoUiConfirmDialog, { open: true, onOpenChange: (is_open) => { if (!is_open)
|
|
255
|
+
handle_cancel_delete(); }, title: t('list_editor.delete_confirm_title', { name: delete_item_name }), description: delete_message, variant: "destructive", confirmLabel: t('common.delete'), onConfirm: handle_delete_confirm, onCancel: handle_cancel_delete })), on_bulk_delete && (_jsx(HazoUiConfirmDialog, { open: bulk_delete_open, onOpenChange: (is_open) => { if (!is_open)
|
|
256
|
+
set_bulk_delete_open(false); }, title: t('list_editor.bulk_delete_title', {
|
|
257
|
+
count: selection_count,
|
|
258
|
+
label: selection_count === 1 ? item_type_label.toLowerCase() : item_label,
|
|
259
|
+
}), description: t('list_editor.bulk_delete_description'), variant: "destructive", confirmLabel: t('list_editor.bulk_delete_confirm_label', { count: selection_count }), onConfirm: handle_bulk_delete_confirm, onCancel: () => set_bulk_delete_open(false) })), bulk_edit_fields && bulk_edit_fields.length > 0 && (_jsx(BulkEditModal, { open: bulk_edit_open, selected_count: selection_count, columns: columns, bulk_edit_fields: bulk_edit_fields, on_confirm: handle_bulk_edit_confirm, on_cancel: () => set_bulk_edit_open(false) }))] }));
|
|
256
260
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk_edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/bulk_edit_modal.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bulk_edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/bulk_edit_modal.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,IAAI,EAAE,OAAO,CAAA;IACb,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,gBAAgB,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAA;IACtC,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/D,IAAI,EACJ,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,UAAU,EACV,SAAS,GACV,EAAE,kBAAkB,CAAC,CAAC,CAAC,+BAuOvB"}
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// Allows editing shared fields across multiple selected items
|
|
5
5
|
import { useState, useCallback, useEffect } from 'react';
|
|
6
6
|
import { cn } from 'hazo_ui';
|
|
7
|
+
import { t } from '../../../lib/strings.js';
|
|
7
8
|
import { Input } from '../../ui/input.js';
|
|
8
9
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../../ui/dialog.js';
|
|
9
10
|
import { Button } from '../../ui/button.js';
|
|
@@ -64,7 +65,7 @@ export function BulkEditModal({ open, selected_count, columns, bulk_edit_fields,
|
|
|
64
65
|
return (_jsx("button", { type: "button", role: "switch", "aria-checked": Boolean(value), disabled: !is_enabled, onClick: () => update_field(col.field, !value), className: cn('relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors', value ? 'bg-primary' : 'bg-gray-200', !is_enabled && 'opacity-40 cursor-not-allowed'), children: _jsx("span", { className: cn('pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition-transform', value ? 'translate-x-4' : 'translate-x-0') }) }));
|
|
65
66
|
}
|
|
66
67
|
if (col.type === 'select' && col.options) {
|
|
67
|
-
return (_jsxs("select", { value: String(value ?? ''), disabled: !is_enabled, onChange: (e) => update_field(col.field, e.target.value), className: cn('flex h-10 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/20 focus-visible:border-violet-300', !is_enabled && 'opacity-40 cursor-not-allowed bg-gray-50'), children: [_jsx("option", { value: "", children: col.placeholder || '
|
|
68
|
+
return (_jsxs("select", { value: String(value ?? ''), disabled: !is_enabled, onChange: (e) => update_field(col.field, e.target.value), className: cn('flex h-10 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/20 focus-visible:border-violet-300', !is_enabled && 'opacity-40 cursor-not-allowed bg-gray-50'), children: [_jsx("option", { value: "", children: col.placeholder || t('common.select_placeholder') }), col.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }));
|
|
68
69
|
}
|
|
69
70
|
if (col.type === 'textarea') {
|
|
70
71
|
return (_jsx("textarea", { value: String(value ?? ''), disabled: !is_enabled, onChange: (e) => update_field(col.field, e.target.value), placeholder: col.placeholder, rows: 3, className: cn('flex w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/20 focus-visible:border-violet-300 disabled:cursor-not-allowed disabled:opacity-50', !is_enabled && 'opacity-40 bg-gray-50') }));
|
|
@@ -76,8 +77,8 @@ export function BulkEditModal({ open, selected_count, columns, bulk_edit_fields,
|
|
|
76
77
|
return (_jsx(Input, { type: "text", value: String(value ?? ''), disabled: !is_enabled, onChange: (e) => update_field(col.field, e.target.value), placeholder: col.placeholder, className: cn('rounded-lg focus-visible:ring-violet-500/20 focus-visible:ring-offset-0', !is_enabled && 'opacity-40 bg-gray-50') }));
|
|
77
78
|
};
|
|
78
79
|
return (_jsx(Dialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
|
|
79
|
-
on_cancel(); }, children: _jsxs(DialogContent, { className: "cls_bulk_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col sm:max-w-lg", children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: [
|
|
80
|
+
on_cancel(); }, children: _jsxs(DialogContent, { className: "cls_bulk_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col sm:max-w-lg", children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: [_jsx(DialogTitle, { children: t('bulk_edit.title', { count: selected_count }) }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children: t('bulk_edit.description') })] }), _jsxs("div", { className: "px-6 pb-4 space-y-3 overflow-y-auto flex-1 min-h-0", children: [editable_cols.map((col) => {
|
|
80
81
|
const is_enabled = enabled_fields.has(col.field);
|
|
81
|
-
return (_jsxs("div", { className: "cls_bulk_edit_field", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [_jsx("input", { type: "checkbox", id: `bulk_enable_${col.field}`, checked: is_enabled, onChange: () => toggle_field_enabled(col.field), className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500" }), _jsxs("label", { htmlFor: `bulk_enable_${col.field}`, className: cn('text-sm font-medium cursor-pointer select-none', is_enabled ? 'text-gray-700' : 'text-gray-400'), children: [col.label, !is_enabled && (_jsx("span", { className: "ml-2 text-xs font-normal text-gray-400", children:
|
|
82
|
-
}), editable_cols.length === 0 && (_jsx("p", { className: "text-sm text-gray-400 py-4 text-center", children:
|
|
82
|
+
return (_jsxs("div", { className: "cls_bulk_edit_field", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [_jsx("input", { type: "checkbox", id: `bulk_enable_${col.field}`, checked: is_enabled, onChange: () => toggle_field_enabled(col.field), className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500" }), _jsxs("label", { htmlFor: `bulk_enable_${col.field}`, className: cn('text-sm font-medium cursor-pointer select-none', is_enabled ? 'text-gray-700' : 'text-gray-400'), children: [col.label, !is_enabled && (_jsx("span", { className: "ml-2 text-xs font-normal text-gray-400", children: t('bulk_edit.leave_unchanged') }))] })] }), _jsx("div", { className: cn('transition-opacity', !is_enabled && 'opacity-50'), children: col.type === 'toggle' ? (_jsxs("div", { className: "flex items-center justify-between py-1 pl-6", children: [_jsx("span", { className: "text-sm text-gray-500", children: col.label }), render_field_control(col)] })) : (_jsx("div", { className: "pl-6", children: render_field_control(col) })) })] }, col.field));
|
|
83
|
+
}), editable_cols.length === 0 && (_jsx("p", { className: "text-sm text-gray-400 py-4 text-center", children: t('bulk_edit.no_fields') }))] }), _jsxs(DialogFooter, { className: "bg-gray-50 px-6 py-4 border-t border-gray-100 sm:justify-between shrink-0", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: on_cancel, className: "rounded-full", children: t('common.cancel') }), _jsx(Button, { type: "button", onClick: handle_confirm, disabled: enabled_fields.size === 0, className: "rounded-full bg-violet-600 hover:bg-violet-700 text-white disabled:opacity-50 disabled:cursor-not-allowed", children: t('bulk_edit.apply', { count: selected_count }) })] })] }) }));
|
|
83
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"color_swatch_picker.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/color_swatch_picker.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"color_swatch_picker.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/color_swatch_picker.tsx"],"names":[],"mappings":"AAOA,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,OAAO,EACP,SAAS,EACT,SAAS,GACV,EAAE,sBAAsB,+BA+BxB"}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
// Displays a row of circular color swatches for selection
|
|
4
4
|
import { Check } from 'lucide-react';
|
|
5
5
|
import { cn } from 'hazo_ui';
|
|
6
|
+
import { t } from '../../../lib/strings.js';
|
|
6
7
|
export function ColorSwatchPicker({ value, options, on_change, className, }) {
|
|
7
8
|
return (_jsx("div", { className: cn('cls_color_swatch_picker flex flex-wrap gap-2', className), children: options.map((color) => {
|
|
8
9
|
const is_selected = value === color;
|
|
@@ -10,6 +11,6 @@ export function ColorSwatchPicker({ value, options, on_change, className, }) {
|
|
|
10
11
|
const bg_class = color.split(' ').find(c => c.startsWith('bg-')) || 'bg-gray-200';
|
|
11
12
|
return (_jsx("button", { type: "button", onClick: () => on_change(color), className: cn('cls_color_swatch w-7 h-7 rounded-full flex items-center justify-center transition-transform hover:scale-110', bg_class, is_selected
|
|
12
13
|
? 'ring-2 ring-violet-600 ring-offset-2'
|
|
13
|
-
: 'ring-1 ring-gray-200'), "aria-label":
|
|
14
|
+
: 'ring-1 ring-gray-200'), "aria-label": t('color_swatch.select_aria', { color }), "aria-pressed": is_selected, children: is_selected && (_jsx(Check, { className: "w-3.5 h-3.5 text-current opacity-80" })) }, color));
|
|
14
15
|
}) }));
|
|
15
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete_dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/delete_dialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"delete_dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/delete_dialog.tsx"],"names":[],"mappings":"AAgBA,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAU,EACV,SAAS,GACV,EAAE,iBAAiB,+BAgCnB"}
|
|
@@ -2,8 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
// Delete confirmation dialog component
|
|
3
3
|
// Uses shadcn AlertDialog with red warning styling
|
|
4
4
|
import { AlertTriangle } from 'lucide-react';
|
|
5
|
+
import { t } from '../../../lib/strings.js';
|
|
5
6
|
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, } from '../../ui/alert-dialog.js';
|
|
6
7
|
export function DeleteDialog({ open, item_name, message, on_confirm, on_cancel, }) {
|
|
7
8
|
return (_jsx(AlertDialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
|
|
8
|
-
on_cancel(); }, children: _jsxs(AlertDialogContent, { className: "cls_delete_dialog max-w-sm rounded-2xl", children: [_jsxs(AlertDialogHeader, { className: "items-center sm:items-center", children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mb-2", children: _jsx(AlertTriangle, { className: "w-6 h-6 text-red-600" }) }), _jsxs(AlertDialogTitle, { className: "text-center", children: ["Delete \u201C", item_name, "\u201D?"] }), _jsx(AlertDialogDescription, { className: "text-center", children: message })] }), _jsxs(AlertDialogFooter, { className: "sm:justify-center gap-2", children: [_jsx(AlertDialogCancel, { onClick: on_cancel, className: "rounded-full", children:
|
|
9
|
+
on_cancel(); }, children: _jsxs(AlertDialogContent, { className: "cls_delete_dialog max-w-sm rounded-2xl", children: [_jsxs(AlertDialogHeader, { className: "items-center sm:items-center", children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mb-2", children: _jsx(AlertTriangle, { className: "w-6 h-6 text-red-600" }) }), _jsxs(AlertDialogTitle, { className: "text-center", children: ["Delete \u201C", item_name, "\u201D?"] }), _jsx(AlertDialogDescription, { className: "text-center", children: message })] }), _jsxs(AlertDialogFooter, { className: "sm:justify-center gap-2", children: [_jsx(AlertDialogCancel, { onClick: on_cancel, className: "rounded-full", children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: on_confirm, className: "rounded-full bg-red-600 text-white hover:bg-red-700", children: t('common.delete') })] })] }) }));
|
|
9
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/edit_modal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAA2C,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/edit_modal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAA2C,MAAM,OAAO,CAAA;AAe/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAQ5C,UAAU,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxD,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAA;IACvB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAChB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC/B,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7C,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;IAC1B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3D,IAAI,EACJ,IAAI,EACJ,IAAI,EAAE,YAAY,EAClB,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,wBAAwB,EACxB,YAAY,EACZ,eAAe,EACf,cAAc,EACd,OAAO,EACP,SAAS,EACT,eAAgC,EAChC,SAAiB,GAClB,EAAE,cAAc,CAAC,CAAC,CAAC,qBA2UnB"}
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// Centered dialog for creating/editing items with dynamic form fields
|
|
5
5
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
6
6
|
import { cn } from 'hazo_ui';
|
|
7
|
+
import { t } from '../../../lib/strings.js';
|
|
7
8
|
import { Input } from '../../ui/input.js';
|
|
8
9
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../../ui/dialog.js';
|
|
9
10
|
import { Button } from '../../ui/button.js';
|
|
@@ -56,7 +57,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
|
|
|
56
57
|
const value = form_data[col.field];
|
|
57
58
|
// Required check
|
|
58
59
|
if (col.required && (value === undefined || value === null || value === '')) {
|
|
59
|
-
new_errors.set(col.field,
|
|
60
|
+
new_errors.set(col.field, t('edit_modal.field_required', { label: col.label }));
|
|
60
61
|
continue;
|
|
61
62
|
}
|
|
62
63
|
// Custom validation
|
|
@@ -70,7 +71,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
|
|
|
70
71
|
// Check ID field is filled
|
|
71
72
|
const id_value = form_data[id_field];
|
|
72
73
|
if (!id_value || String(id_value).trim() === '') {
|
|
73
|
-
new_errors.set(id_field, '
|
|
74
|
+
new_errors.set(id_field, t('edit_modal.id_required'));
|
|
74
75
|
}
|
|
75
76
|
// Check for duplicate ID
|
|
76
77
|
if (id_value) {
|
|
@@ -79,7 +80,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
|
|
|
79
80
|
? existing_ids.includes(id_str)
|
|
80
81
|
: existing_ids.filter(id => id !== String(initial_item[id_field])).includes(id_str);
|
|
81
82
|
if (is_duplicate) {
|
|
82
|
-
new_errors.set(id_field, '
|
|
83
|
+
new_errors.set(id_field, t('edit_modal.id_duplicate'));
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
set_errors(new_errors);
|
|
@@ -119,11 +120,17 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
|
|
|
119
120
|
return (_jsxs("div", { className: "cls_edit_field space-y-1.5", children: [_jsxs("label", { className: "text-sm font-medium text-gray-700", children: [col.label, col.required && _jsx("span", { className: "text-red-500 ml-0.5", children: "*" })] }), col.type === 'text' && (_jsx(Input, { type: "text", value: String(value ?? ''), onChange: (e) => is_id_field
|
|
120
121
|
? handle_id_change(e.target.value)
|
|
121
122
|
: update_field(col.field, e.target.value), placeholder: col.placeholder, disabled: is_disabled, className: cn('rounded-lg focus-visible:ring-violet-500/20 focus-visible:ring-offset-0', is_disabled && 'bg-gray-50 text-gray-500', error && 'border-red-400 focus-visible:ring-red-500/20') })), col.type === 'number' && (_jsx(Input, { type: "number", value: value !== undefined && value !== null ? String(value) : '', onChange: (e) => update_field(col.field, e.target.value === '' ? '' : Number(e.target.value)), placeholder: col.placeholder, className: cn('rounded-lg focus-visible:ring-violet-500/20 focus-visible:ring-offset-0', error && 'border-red-400 focus-visible:ring-red-500/20') })), col.type === 'textarea' && (_jsx("textarea", { value: String(value ?? ''), onChange: (e) => update_field(col.field, e.target.value), placeholder: col.placeholder, rows: 3, className: cn('flex w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/20 focus-visible:border-violet-300 disabled:cursor-not-allowed disabled:opacity-50', error && 'border-red-400 focus-visible:ring-red-500/20') })), col.type === 'select' && col.options && ((col.searchable || col.options.length > 10)
|
|
122
|
-
? (_jsx(Combobox, { value: String(value ?? ''), onChange: (v) => update_field(col.field, v), options: [{ value: '', label: col.placeholder || '
|
|
123
|
-
: (_jsxs(Select, { value: String(value ?? ''), onValueChange: (v) => update_field(col.field, v === SELECT_CLEAR_VALUE ? '' : v), children: [_jsx(SelectTrigger, { className: cn(error && 'border-red-400'), children: _jsx(SelectValue, { placeholder: col.placeholder || '
|
|
123
|
+
? (_jsx(Combobox, { value: String(value ?? ''), onChange: (v) => update_field(col.field, v), options: [{ value: '', label: col.placeholder || t('common.select_placeholder') }, ...col.options], placeholder: col.placeholder || t('common.select_placeholder'), error: !!error }))
|
|
124
|
+
: (_jsxs(Select, { value: String(value ?? ''), onValueChange: (v) => update_field(col.field, v === SELECT_CLEAR_VALUE ? '' : v), children: [_jsx(SelectTrigger, { className: cn(error && 'border-red-400'), children: _jsx(SelectValue, { placeholder: col.placeholder || t('common.select_placeholder') }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: SELECT_CLEAR_VALUE, children: col.placeholder || t('common.select_placeholder') }), col.options.map((opt) => (_jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)))] })] }))), col.type === 'color_swatch' && col.color_options && (_jsx(ColorSwatchPicker, { value: String(value ?? ''), options: col.color_options, on_change: (color) => update_field(col.field, color) })), is_id_field && auto_id_from && mode === 'create' && !user_touched_id && (_jsx("p", { className: "text-xs text-gray-400", children: t('edit_modal.auto_generated_hint', { source: columns.find(c => c.field === auto_id_from)?.label?.toLowerCase() || auto_id_from }) })), error && (_jsx("p", { className: "text-xs text-red-600", children: error }))] }, col.field));
|
|
124
125
|
};
|
|
125
126
|
return (_jsx(Dialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
|
|
126
|
-
on_cancel(); }, children: _jsxs(DialogContent, { className: cn('cls_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col', max_width_class), children: [_jsx(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: _jsx(DialogTitle, { children: mode === 'create'
|
|
127
|
+
on_cancel(); }, children: _jsxs(DialogContent, { className: cn('cls_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col', max_width_class), children: [_jsx(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: _jsx(DialogTitle, { children: mode === 'create'
|
|
128
|
+
? t('edit_modal.title_new', { item_type: item_type_label })
|
|
129
|
+
: t('edit_modal.title_edit', { item_type: item_type_label }) }) }), _jsxs("div", { className: "px-6 pb-4 space-y-4 overflow-y-auto flex-1 min-h-0", children: [render_preview && Object.keys(form_data).length > 0 && (_jsx("div", { className: "p-3 bg-gray-50 rounded-lg border border-gray-100", children: render_preview(form_data) })), columns.map((col) => render_field(col))] }), _jsxs(DialogFooter, { className: "bg-gray-50 px-6 py-4 border-t border-gray-100 sm:justify-between shrink-0", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: on_cancel, className: "rounded-full", children: t('common.cancel') }), _jsx(Button, { type: "button", onClick: handle_save, disabled: is_saving, className: "rounded-full bg-violet-600 hover:bg-violet-700 text-white disabled:opacity-50 disabled:cursor-not-allowed", children: is_saving
|
|
130
|
+
? t('edit_modal.saving')
|
|
131
|
+
: mode === 'create'
|
|
132
|
+
? t('edit_modal.add_button', { item_type: item_type_label })
|
|
133
|
+
: t('edit_modal.save_changes') })] })] }) }));
|
|
127
134
|
}
|
|
128
135
|
/* ── Tag Picker Field ── */
|
|
129
136
|
function TagPickerField({ value, options, on_change }) {
|
|
@@ -151,5 +158,5 @@ function TagPickerField({ value, options, on_change }) {
|
|
|
151
158
|
on_change(value.filter(v => v !== tag_value));
|
|
152
159
|
};
|
|
153
160
|
const label_for = (v) => options.find(o => o.value === v)?.label ?? v;
|
|
154
|
-
return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("div", { className: cn('flex flex-wrap gap-1.5 min-h-[38px] p-1.5 border rounded-lg bg-white cursor-text', open && 'ring-2 ring-violet-500/20 border-violet-400'), onClick: () => set_open(true), children: [value.map(v => (_jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-violet-100 text-violet-800 text-xs font-medium", children: [label_for(v), _jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); remove_tag(v); }, className: "text-violet-500 hover:text-violet-800 ml-0.5", children: "\u00D7" })] }, v))), _jsx("input", { type: "text", value: search, onChange: e => { set_search(e.target.value); set_open(true); }, onFocus: () => set_open(true), placeholder: value.length === 0 ? '
|
|
161
|
+
return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("div", { className: cn('flex flex-wrap gap-1.5 min-h-[38px] p-1.5 border rounded-lg bg-white cursor-text', open && 'ring-2 ring-violet-500/20 border-violet-400'), onClick: () => set_open(true), children: [value.map(v => (_jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-violet-100 text-violet-800 text-xs font-medium", children: [label_for(v), _jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); remove_tag(v); }, className: "text-violet-500 hover:text-violet-800 ml-0.5", children: "\u00D7" })] }, v))), _jsx("input", { type: "text", value: search, onChange: e => { set_search(e.target.value); set_open(true); }, onFocus: () => set_open(true), placeholder: value.length === 0 ? t('edit_modal.tag_search_placeholder') : '', className: "flex-1 min-w-[80px] text-sm outline-none bg-transparent px-1 py-0.5" })] }), open && filtered.length > 0 && (_jsx("div", { className: "absolute z-50 mt-1 w-full max-h-48 overflow-y-auto rounded-lg border bg-white shadow-lg", children: filtered.map(opt => (_jsxs("button", { type: "button", onClick: () => add_tag(opt.value), className: "w-full text-left px-3 py-2 text-sm hover:bg-violet-50 transition-colors flex items-center justify-between", children: [_jsx("span", { children: opt.label }), _jsx("span", { className: "text-xs text-gray-400 font-mono", children: opt.value })] }, opt.value))) })), open && filtered.length === 0 && search && (_jsx("div", { className: "absolute z-50 mt-1 w-full rounded-lg border bg-white shadow-lg px-3 py-2 text-sm text-gray-400", children: t('edit_modal.no_matching_options') }))] }));
|
|
155
162
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empty_state.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/empty_state.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"empty_state.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/empty_state.tsx"],"names":[],"mappings":"AAOA,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,MAAM,EACN,SAAS,GACV,EAAE,eAAe,+BAsBjB"}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
// Displayed when the list has no items
|
|
4
4
|
import { Inbox, Plus } from 'lucide-react';
|
|
5
5
|
import { cn } from 'hazo_ui';
|
|
6
|
+
import { t } from '../../../lib/strings.js';
|
|
6
7
|
export function EmptyState({ item_label, on_add, className, }) {
|
|
7
|
-
return (_jsxs("div", { className: cn('cls_empty_state flex flex-col items-center justify-center py-12 px-4', className), children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center mb-3", children: _jsx(Inbox, { className: "w-6 h-6 text-gray-400" }) }),
|
|
8
|
+
return (_jsxs("div", { className: cn('cls_empty_state flex flex-col items-center justify-center py-12 px-4', className), children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center mb-3", children: _jsx(Inbox, { className: "w-6 h-6 text-gray-400" }) }), _jsx("p", { className: "text-sm font-medium text-gray-900 mb-1", children: t('empty_state.title', { item_label }) }), _jsx("p", { className: "text-sm text-gray-500 mb-4", children: t('empty_state.description', { item: item_label.replace(/s$/, '') }) }), _jsxs("button", { type: "button", onClick: on_add, className: "inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-violet-600 hover:bg-violet-700 rounded-full transition-colors", children: [_jsx(Plus, { className: "w-4 h-4" }), t('empty_state.cta', { item: item_label.replace(/s$/, '') })] })] }));
|
|
8
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import_export_buttons.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/import_export_buttons.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import_export_buttons.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/import_export_buttons.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAQ5C,UAAU,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,eAAe,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACrE,KAAK,EACL,eAAe,EACf,OAAO,EACP,QAAQ,EACR,KAAK,EACL,SAAS,GACV,EAAE,wBAAwB,CAAC,CAAC,CAAC,+BA0H7B"}
|
|
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
6
6
|
import { Download, Upload } from 'lucide-react';
|
|
7
7
|
import { cn } from 'hazo_ui';
|
|
8
|
+
import { t } from '../../../lib/strings.js';
|
|
8
9
|
import { validate_import_data, merge_items, export_items_to_json, generate_export_filename, } from '../utils/json_import_export.js';
|
|
9
10
|
export function ImportExportButtons({ items, on_items_change, columns, id_field, title, max_items, }) {
|
|
10
11
|
const file_input_ref = useRef(null);
|
|
@@ -35,7 +36,7 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
|
|
|
35
36
|
reader.onload = (e) => {
|
|
36
37
|
const text = e.target?.result;
|
|
37
38
|
if (typeof text !== 'string') {
|
|
38
|
-
set_status_message({ text: '
|
|
39
|
+
set_status_message({ text: t('import_export.error_read_file'), is_error: true });
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
let data;
|
|
@@ -43,12 +44,12 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
|
|
|
43
44
|
data = JSON.parse(text);
|
|
44
45
|
}
|
|
45
46
|
catch {
|
|
46
|
-
set_status_message({ text: '
|
|
47
|
+
set_status_message({ text: t('import_export.error_invalid_json'), is_error: true });
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
const { valid_items, errors } = validate_import_data(data, columns, id_field);
|
|
50
51
|
if (valid_items.length === 0) {
|
|
51
|
-
const msg = errors.length > 0 ? errors[0] : '
|
|
52
|
+
const msg = errors.length > 0 ? errors[0] : t('import_export.error_no_valid_items');
|
|
52
53
|
set_status_message({ text: msg, is_error: true });
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
@@ -56,13 +57,13 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
|
|
|
56
57
|
on_items_change(result.merged_items);
|
|
57
58
|
const parts = [];
|
|
58
59
|
if (result.added_count > 0) {
|
|
59
|
-
parts.push(
|
|
60
|
+
parts.push(t('import_export.added', { count: result.added_count }));
|
|
60
61
|
}
|
|
61
62
|
if (result.skipped_count > 0) {
|
|
62
|
-
parts.push(
|
|
63
|
+
parts.push(t('import_export.skipped', { count: result.skipped_count }));
|
|
63
64
|
}
|
|
64
65
|
if (errors.length > 0) {
|
|
65
|
-
parts.push(
|
|
66
|
+
parts.push(t('import_export.invalid', { count: errors.length }));
|
|
66
67
|
}
|
|
67
68
|
set_status_message({
|
|
68
69
|
text: parts.join(', '),
|
|
@@ -71,5 +72,5 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
|
|
|
71
72
|
};
|
|
72
73
|
reader.readAsText(file);
|
|
73
74
|
}, [items, columns, id_field, max_items, on_items_change]);
|
|
74
|
-
return (_jsxs("div", { className: "cls_import_export flex items-center gap-1.5", children: [status_message && (_jsx("span", { className: cn('cls_import_status text-xs transition-opacity duration-300', status_message.is_error ? 'text-red-600' : 'text-green-600'), children: status_message.text })), _jsxs("button", { type: "button", onClick: handle_export, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title:
|
|
75
|
+
return (_jsxs("div", { className: "cls_import_export flex items-center gap-1.5", children: [status_message && (_jsx("span", { className: cn('cls_import_status text-xs transition-opacity duration-300', status_message.is_error ? 'text-red-600' : 'text-green-600'), children: status_message.text })), _jsxs("button", { type: "button", onClick: handle_export, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title: t('import_export.export_title'), children: [_jsx(Download, { className: "w-3.5 h-3.5" }), t('import_export.export')] }), _jsxs("button", { type: "button", onClick: handle_import_click, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title: t('import_export.import_title'), children: [_jsx(Upload, { className: "w-3.5 h-3.5" }), t('import_export.import')] }), _jsx("input", { ref: file_input_ref, type: "file", accept: ".json", onChange: handle_file_selected, className: "hidden" })] }));
|
|
75
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list_item_row.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/list_item_row.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"list_item_row.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/list_item_row.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,UAAU,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACzD,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IACpD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC7D,IAAI,EACJ,KAAK,EACL,OAAO,EACP,WAAW,EACX,qBAAqB,EACrB,OAAO,EACP,SAAS,EACT,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,EAAE,gBAAgB,CAAC,CAAC,CAAC,qBA0HrB"}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Pencil, Trash2 } from 'lucide-react';
|
|
3
3
|
import { cn } from 'hazo_ui';
|
|
4
|
+
import { t } from '../../../lib/strings.js';
|
|
4
5
|
export function ListItemRow({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, show_checkbox, is_selected, on_toggle_select, }) {
|
|
5
6
|
// Custom render overrides everything
|
|
6
7
|
if (render_item) {
|
|
7
|
-
return (_jsxs("div", { className: "cls_list_editor_row flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors", children: [show_checkbox && (_jsx("input", { type: "checkbox", checked: is_selected ?? false, onChange: on_toggle_select, className: "shrink-0 w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label":
|
|
8
|
+
return (_jsxs("div", { className: "cls_list_editor_row flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors", children: [show_checkbox && (_jsx("input", { type: "checkbox", checked: is_selected ?? false, onChange: on_toggle_select, className: "shrink-0 w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": t('list_item.select_item_aria') })), _jsx("div", { className: "flex-1 min-w-0", children: render_item(item, index) }), _jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [_jsx("button", { type: "button", onClick: on_edit, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors", "aria-label": t('list_item.edit_item_aria'), children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx("button", { type: "button", onClick: on_delete, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors", "aria-label": t('list_item.delete_item_aria'), children: _jsx(Trash2, { className: "w-4 h-4" }) })] })] }));
|
|
8
9
|
}
|
|
9
10
|
// Default rendering based on column definitions
|
|
10
11
|
const primary_cols = columns.filter(c => c.list_display === 'primary');
|
|
11
12
|
const secondary_cols = columns.filter(c => c.list_display === 'secondary');
|
|
12
13
|
const badge_cols = columns.filter(c => c.list_display === 'badge');
|
|
13
|
-
return (_jsxs("div", { className: "cls_list_editor_row flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors", children: [show_checkbox && (_jsx("input", { type: "checkbox", checked: is_selected ?? false, onChange: on_toggle_select, className: "shrink-0 w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label":
|
|
14
|
+
return (_jsxs("div", { className: "cls_list_editor_row flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors", children: [show_checkbox && (_jsx("input", { type: "checkbox", checked: is_selected ?? false, onChange: on_toggle_select, className: "shrink-0 w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": t('list_item.select_item_aria') })), render_item_indicator && (_jsx("div", { className: "shrink-0", children: render_item_indicator(item) })), _jsxs("div", { className: "flex-1 min-w-0", children: [primary_cols.map((col) => (_jsx("div", { className: "text-sm font-medium text-gray-900 truncate", children: col.format?.(item[col.field], item) ?? String(item[col.field] ?? '') }, col.field))), secondary_cols.map((col) => (_jsx("div", { className: "text-xs text-gray-500 truncate mt-0.5", children: col.format?.(item[col.field], item) ?? String(item[col.field] ?? '') }, col.field)))] }), badge_cols.length > 0 && (_jsx("div", { className: "flex items-center gap-2 shrink-0", children: badge_cols.map((col) => {
|
|
14
15
|
const badge_class = typeof col.badge_class === 'function'
|
|
15
16
|
? col.badge_class(item[col.field], item)
|
|
16
17
|
: col.badge_class;
|
|
17
18
|
return (_jsx("span", { className: cn('inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-mono', badge_class || 'bg-gray-100 text-gray-600'), children: col.format?.(item[col.field], item) ?? String(item[col.field] ?? '') }, col.field));
|
|
18
|
-
}) })), _jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [_jsx("button", { type: "button", onClick: on_edit, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors", "aria-label":
|
|
19
|
+
}) })), _jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [_jsx("button", { type: "button", onClick: on_edit, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors", "aria-label": t('list_item.edit_item_aria'), children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx("button", { type: "button", onClick: on_delete, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors", "aria-label": t('list_item.delete_item_aria'), children: _jsx(Trash2, { className: "w-4 h-4" }) })] })] }));
|
|
19
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save_status_indicator.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/save_status_indicator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"save_status_indicator.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/save_status_indicator.tsx"],"names":[],"mappings":"AAUA,UAAU,wBAAwB;IAChC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,SAAS,GACV,EAAE,wBAAwB,sCA+C1B"}
|
|
@@ -5,6 +5,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
5
5
|
import { useEffect, useState } from 'react';
|
|
6
6
|
import { Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
7
7
|
import { cn } from 'hazo_ui';
|
|
8
|
+
import { t } from '../../../lib/strings.js';
|
|
8
9
|
export function SaveStatusIndicator({ status, className, }) {
|
|
9
10
|
const [visible, set_visible] = useState(false);
|
|
10
11
|
useEffect(() => {
|
|
@@ -22,5 +23,5 @@ export function SaveStatusIndicator({ status, className, }) {
|
|
|
22
23
|
}, [status]);
|
|
23
24
|
if (!visible && status === 'idle')
|
|
24
25
|
return null;
|
|
25
|
-
return (_jsxs("div", { className: cn('cls_save_status flex items-center gap-1.5 text-xs transition-opacity duration-300', !visible && 'opacity-0', visible && 'opacity-100', className), children: [status === 'saving' && (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-3.5 h-3.5 text-gray-500 animate-spin" }), _jsx("span", { className: "text-gray-500", children:
|
|
26
|
+
return (_jsxs("div", { className: cn('cls_save_status flex items-center gap-1.5 text-xs transition-opacity duration-300', !visible && 'opacity-0', visible && 'opacity-100', className), children: [status === 'saving' && (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-3.5 h-3.5 text-gray-500 animate-spin" }), _jsx("span", { className: "text-gray-500", children: t('save_status.saving') })] })), status === 'saved' && (_jsxs(_Fragment, { children: [_jsx(CheckCircle2, { className: "w-3.5 h-3.5 text-green-600" }), _jsx("span", { className: "text-green-600", children: t('save_status.saved') })] })), status === 'error' && (_jsxs(_Fragment, { children: [_jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-600" }), _jsx("span", { className: "text-red-600", children: t('save_status.error') })] }))] }));
|
|
26
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search_bar.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/search_bar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search_bar.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/search_bar.tsx"],"names":[],"mappings":"AAOA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,GACV,EAAE,cAAc,+BA6BhB"}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
// Pill-shaped search input with icon, clear button, and item count
|
|
4
4
|
import { Search, X } from 'lucide-react';
|
|
5
5
|
import { cn } from 'hazo_ui';
|
|
6
|
+
import { t } from '../../../lib/strings.js';
|
|
6
7
|
export function SearchBar({ value, on_change, item_count, item_label, className, }) {
|
|
7
|
-
return (_jsxs("div", { className: cn('cls_search_bar flex items-center gap-3', className), children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" }), _jsx("input", { type: "text", value: value, onChange: (e) => on_change(e.target.value), placeholder:
|
|
8
|
+
return (_jsxs("div", { className: cn('cls_search_bar flex items-center gap-3', className), children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" }), _jsx("input", { type: "text", value: value, onChange: (e) => on_change(e.target.value), placeholder: t('search.placeholder', { item_label }), className: "w-full pl-9 pr-8 py-2 text-sm rounded-full border border-gray-200 bg-white focus:outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-300 transition-colors", "aria-label": t('search.aria_label', { item_label }) }), value && (_jsx("button", { type: "button", onClick: () => on_change(''), className: "absolute right-2.5 top-1/2 -translate-y-1/2 w-5 h-5 flex items-center justify-center rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100", "aria-label": t('search.clear_aria_label'), children: _jsx(X, { className: "w-3.5 h-3.5" }) }))] }), _jsxs("span", { className: "text-sm text-gray-500 whitespace-nowrap", children: [item_count, " ", item_label] })] }));
|
|
8
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config_editor.d.ts","sourceRoot":"","sources":["../../src/components/config_editor.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"config_editor.d.ts","sourceRoot":"","sources":["../../src/components/config_editor.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAKrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAWD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgYpD,CAAA"}
|
|
@@ -6,6 +6,7 @@ import React, { useState, useCallback, useMemo } from 'react';
|
|
|
6
6
|
import { cn } from 'hazo_ui';
|
|
7
7
|
import { RefreshCw, Save, Trash2, Eye, EyeOff } from 'lucide-react';
|
|
8
8
|
import { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
|
|
9
|
+
import { t } from '../lib/strings.js';
|
|
9
10
|
/**
|
|
10
11
|
* Config editor component
|
|
11
12
|
* Provides interface for viewing and editing configuration with refresh and save capabilities
|
|
@@ -208,9 +209,9 @@ export const ConfigEditor = ({ config_provider, className, on_update, on_error,
|
|
|
208
209
|
const current_section_data = useMemo(() => {
|
|
209
210
|
return selected_section ? sections[selected_section] : null;
|
|
210
211
|
}, [selected_section, sections]);
|
|
211
|
-
return (_jsxs("div", { className: cn('cls_config_editor space-y-4', className), children: [_jsxs("div", { className: "cls_config_editor_header flex items-center justify-between border-b pb-2", children: [_jsxs("h2", { className: "text-xl font-semibold", children: [
|
|
212
|
+
return (_jsxs("div", { className: cn('cls_config_editor space-y-4', className), children: [_jsxs("div", { className: "cls_config_editor_header flex items-center justify-between border-b pb-2", children: [_jsxs("h2", { className: "text-xl font-semibold", children: [t('config_editor.title'), has_unsaved_changes && (_jsx("span", { className: "ml-2 text-sm text-amber-600", children: t('config_editor.unsaved_suffix') }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: handle_refresh, className: "px-3 py-1 border rounded hover:bg-muted flex items-center gap-2", "aria-label": t('config_editor.refresh_aria'), type: "button", children: [_jsx(RefreshCw, { size: 16 }), t('config_editor.refresh')] }), _jsxs("button", { onClick: handle_save, className: cn('px-3 py-1 rounded flex items-center gap-2', has_unsaved_changes
|
|
212
213
|
? 'bg-primary text-primary-foreground hover:bg-primary/90'
|
|
213
|
-
: 'bg-muted text-muted-foreground'), "aria-label":
|
|
214
|
+
: 'bg-muted text-muted-foreground'), "aria-label": t('config_editor.save_aria'), type: "button", disabled: !has_unsaved_changes, children: [_jsx(Save, { size: 16 }), t('config_editor.save')] })] })] }), _jsxs("div", { className: "cls_config_editor_content grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "cls_config_sections_list border rounded p-4", children: [_jsx("h3", { className: "font-semibold mb-2", children: t('config_editor.sections_heading') }), _jsx("div", { className: "space-y-1", role: "listbox", "aria-label": t('config_editor.sections_aria'), children: section_list.map((section_name) => (_jsx("button", { onClick: () => handle_section_select(section_name), className: cn('w-full text-left px-2 py-1 rounded text-sm', selected_section === section_name
|
|
214
215
|
? 'bg-primary text-primary-foreground'
|
|
215
216
|
: 'hover:bg-muted'), role: "option", "aria-selected": selected_section === section_name, type: "button", children: section_name }, section_name))) })] }), _jsx("div", { className: "cls_config_section_content md:col-span-2 border rounded p-4", children: selected_section ? (_jsxs(_Fragment, { children: [_jsx("h3", { className: "font-semibold mb-4", children: selected_section }), _jsxs("div", { className: "space-y-3", children: [current_section_data &&
|
|
216
217
|
Object.entries(current_section_data).map(([key, value]) => {
|
|
@@ -218,6 +219,6 @@ export const ConfigEditor = ({ config_provider, className, on_update, on_error,
|
|
|
218
219
|
const field_id = `config-${selected_section}-${key}`;
|
|
219
220
|
const show_value = !is_sensitive || is_revealed(selected_section, key);
|
|
220
221
|
const display_value = get_field_value(selected_section, key, value);
|
|
221
|
-
return (_jsxs("div", { className: "cls_config_item", children: [_jsx("label", { htmlFor: field_id, className: "block text-sm font-medium mb-1", children: key }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { id: field_id, type: is_sensitive && !show_value ? 'password' : 'text', value: is_sensitive && !show_value ? mask_value(display_value) : display_value, onChange: (e) => handle_field_change(selected_section, key, e.target.value), className: "flex-1 px-2 py-1 border rounded", readOnly: is_sensitive && !show_value }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(selected_section, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? '
|
|
222
|
-
}), _jsxs("div", { className: "cls_add_new_key border-t pt-3 mt-3", children: [_jsx("h4", { className: "text-sm font-medium mb-2", children:
|
|
222
|
+
return (_jsxs("div", { className: "cls_config_item", children: [_jsx("label", { htmlFor: field_id, className: "block text-sm font-medium mb-1", children: key }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { id: field_id, type: is_sensitive && !show_value ? 'password' : 'text', value: is_sensitive && !show_value ? mask_value(display_value) : display_value, onChange: (e) => handle_field_change(selected_section, key, e.target.value), className: "flex-1 px-2 py-1 border rounded", readOnly: is_sensitive && !show_value }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(selected_section, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? t('common.hide_value') : t('common.show_value'), type: "button", children: show_value ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), _jsx("button", { onClick: () => handle_delete_key(selected_section, key), className: "text-red-500 hover:text-red-700", "aria-label": t('config_editor.delete_key_aria', { key }), type: "button", children: _jsx(Trash2, { size: 18 }) })] })] }, key));
|
|
223
|
+
}), _jsxs("div", { className: "cls_add_new_key border-t pt-3 mt-3", children: [_jsx("h4", { className: "text-sm font-medium mb-2", children: t('config_editor.add_new_key_heading') }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "new-key-name", className: "sr-only", children: t('config_editor.key_name_label') }), _jsx("input", { id: "new-key-name", type: "text", placeholder: t('config_editor.key_name_label'), value: new_key, onChange: handle_new_key_change, className: "w-full px-2 py-1 border rounded text-sm" })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "new-key-value", className: "sr-only", children: t('config_editor.value_label') }), _jsx("input", { id: "new-key-value", type: "text", placeholder: t('config_editor.value_label'), value: new_value, onChange: handle_new_value_change, className: "w-full px-2 py-1 border rounded text-sm" })] }), _jsx("button", { onClick: handle_add_key, className: "px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 text-sm", type: "button", disabled: !new_key || !new_value, children: t('config_editor.add_key') })] })] })] })] })) : (_jsx("div", { className: "text-center text-muted-foreground py-8", children: t('config_editor.select_section_prompt') })) })] })] }));
|
|
223
224
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config_viewer.d.ts","sourceRoot":"","sources":["../../src/components/config_viewer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"config_viewer.d.ts","sourceRoot":"","sources":["../../src/components/config_viewer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAKrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2MpD,CAAA"}
|
|
@@ -6,6 +6,7 @@ import React, { useState, useCallback, useMemo } from 'react';
|
|
|
6
6
|
import { cn } from 'hazo_ui';
|
|
7
7
|
import { Pencil, CheckCircle2, XCircle, Eye, EyeOff } from 'lucide-react';
|
|
8
8
|
import { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
|
|
9
|
+
import { t } from '../lib/strings.js';
|
|
9
10
|
/**
|
|
10
11
|
* Config viewer component
|
|
11
12
|
* Displays configuration sections and allows viewing/editing values
|
|
@@ -108,11 +109,11 @@ export const ConfigViewer = ({ config_provider, className, on_update, on_error,
|
|
|
108
109
|
* Memoized section entries for rendering
|
|
109
110
|
*/
|
|
110
111
|
const section_entries = useMemo(() => Object.entries(sections), [sections]);
|
|
111
|
-
return (_jsx("div", { className: cn('cls_config_viewer space-y-4', className), children: section_entries.length === 0 ? (_jsx("div", { className: "p-4 text-center text-muted-foreground", children:
|
|
112
|
+
return (_jsx("div", { className: cn('cls_config_viewer space-y-4', className), children: section_entries.length === 0 ? (_jsx("div", { className: "p-4 text-center text-muted-foreground", children: t('config_viewer.empty') })) : (section_entries.map(([section_name, section_data]) => (_jsxs("div", { className: "cls_config_section border rounded-lg p-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-3", children: section_name }), _jsx("div", { className: "space-y-2", children: Object.entries(section_data).map(([key, value]) => {
|
|
112
113
|
const is_editing = editing?.section === section_name && editing?.key === key;
|
|
113
114
|
const is_sensitive = should_mask_field(key);
|
|
114
115
|
const field_id = `config-${section_name}-${key}`;
|
|
115
116
|
const show_value = !is_sensitive || is_revealed(section_name, key);
|
|
116
|
-
return (_jsx("div", { className: "cls_config_item flex items-center gap-2", children: _jsxs("div", { className: "flex-1", children: [_jsx("label", { htmlFor: field_id, className: "text-sm font-medium text-muted-foreground", children: key }), is_editing ? (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("input", { id: field_id, type: is_sensitive ? 'password' : 'text', value: edit_value, onChange: handle_edit_change, className: "flex-1 px-2 py-1 border rounded text-sm", autoFocus: true }), _jsx("button", { onClick: save_edit, className: "text-green-600 hover:text-green-700", "aria-label":
|
|
117
|
+
return (_jsx("div", { className: "cls_config_item flex items-center gap-2", children: _jsxs("div", { className: "flex-1", children: [_jsx("label", { htmlFor: field_id, className: "text-sm font-medium text-muted-foreground", children: key }), is_editing ? (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("input", { id: field_id, type: is_sensitive ? 'password' : 'text', value: edit_value, onChange: handle_edit_change, className: "flex-1 px-2 py-1 border rounded text-sm", autoFocus: true }), _jsx("button", { onClick: save_edit, className: "text-green-600 hover:text-green-700", "aria-label": t('config_viewer.save_changes_aria'), type: "button", children: _jsx(CheckCircle2, { size: 20 }) }), _jsx("button", { onClick: cancel_edit, className: "text-red-600 hover:text-red-700", "aria-label": t('config_viewer.cancel_editing_aria'), type: "button", children: _jsx(XCircle, { size: 20 }) })] })) : (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("div", { id: field_id, className: "flex-1 px-2 py-1 bg-muted rounded text-sm font-mono", children: show_value ? (value || t('config_viewer.empty_value')) : mask_value(value) }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(section_name, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? t('common.hide_value') : t('common.show_value'), type: "button", children: show_value ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), _jsx("button", { onClick: () => start_edit(section_name, key), className: "text-blue-600 hover:text-blue-700", "aria-label": t('config_viewer.edit_key_aria', { key }), type: "button", children: _jsx(Pencil, { size: 18 }) })] }))] }) }, key));
|
|
117
118
|
}) })] }, section_name)))) }));
|
|
118
119
|
};
|
|
@@ -3,7 +3,7 @@ import { type VariantProps } from "class-variance-authority";
|
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
4
|
variant?: "default" | "link" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
|
|
5
5
|
size?: "default" | "sm" | "lg" | "icon" | null | undefined;
|
|
6
|
-
} & import("class-variance-authority/
|
|
6
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
8
8
|
asChild?: boolean;
|
|
9
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;8EA2BnB,CAAA;AAED,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EACnD,YAAY,CAAC,OAAO,cAAc,CAAC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,QAAA,MAAM,MAAM,uFAWX,CAAA;AAGD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { MockConfigProvider } from './mock_config_provider.js';
|
|
12
12
|
export { ConfigErrorCode } from './types.js';
|
|
13
|
+
export { t } from './strings.js';
|
|
13
14
|
export type { ConfigProvider, HazoConfigOptions, Logger, HazoConfigError, LogData } from './types.js';
|
|
14
15
|
export type { ConfigType, AppConfigItem, AppConfigContext, AppConfigProps, UseAppConfigResult } from './app_config_types.js';
|
|
15
16
|
export { mergeTwoTier } from './two_tier.js';
|
package/dist/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5C,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,MAAM,EACN,eAAe,EACf,OAAO,EACR,MAAM,YAAY,CAAA;AAGnB,YAAY,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAK9B,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/lib/index.js
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
// Client-safe exports (no Node.js dependencies)
|
|
12
12
|
export { MockConfigProvider } from './mock_config_provider.js';
|
|
13
13
|
export { ConfigErrorCode } from './types.js';
|
|
14
|
+
// i18n — namespaced translator for this package's own baked-in UI strings
|
|
15
|
+
// (config editor/viewer, AppConfig, AppConfigListEditor). See strings.ts.
|
|
16
|
+
export { t } from './strings.js';
|
|
14
17
|
// Two-tier (global default + scope-level override) merge -- pure, no
|
|
15
18
|
// database dependency. See 'hazo_config/server-data' for the hazo_connect-
|
|
16
19
|
// backed clone/override-count helpers that operate on the same shape.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/lib/strings.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,CAAC,gCAA8D,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// hazo_config/src/lib/strings.ts — i18n plumbing for hazo_config's own
|
|
2
|
+
// baked-in UI strings (config editor/viewer, AppConfig, and the
|
|
3
|
+
// AppConfigListEditor family), via hazo_core's canonical bridge
|
|
4
|
+
// (`hazo_core/i18n`).
|
|
5
|
+
//
|
|
6
|
+
// hazo_config's tsconfig.json uses `moduleResolution: "bundler"` (not
|
|
7
|
+
// `Node16`), and `resolveJsonModule: true` is set — so a plain JSON import
|
|
8
|
+
// (no `with { type: 'json' }` attribute) resolves cleanly. tsconfig.build.json
|
|
9
|
+
// previously overrode `moduleResolution` to the classic `"node"` resolver,
|
|
10
|
+
// which cannot follow a package.json `exports` subpath like `hazo_core/i18n`
|
|
11
|
+
// (TS2307) — it was switched to `"bundler"` (matching tsconfig.json) as part
|
|
12
|
+
// of this package's i18n adoption. All of this package's components that use
|
|
13
|
+
// `t()` are `'use client'` components, so a plain import (browser-safe, no
|
|
14
|
+
// `createRequire`) is used rather than the Node-only `createRequire`
|
|
15
|
+
// fallback documented for server-only consumers of this pattern.
|
|
16
|
+
import en from '../locales/en.json';
|
|
17
|
+
import { createPackageT } from 'hazo_core/i18n';
|
|
18
|
+
export const t = createPackageT('hazo_config', en);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common.cancel": "Cancel",
|
|
3
|
+
"common.delete": "Delete",
|
|
4
|
+
"common.select_placeholder": "Select...",
|
|
5
|
+
"common.hide_value": "Hide value",
|
|
6
|
+
"common.show_value": "Show value",
|
|
7
|
+
"save_status.saving": "Saving...",
|
|
8
|
+
"save_status.saved": "Saved",
|
|
9
|
+
"save_status.error": "Error saving",
|
|
10
|
+
"search.placeholder": "Search {{item_label}}...",
|
|
11
|
+
"search.aria_label": "Search {{item_label}}",
|
|
12
|
+
"search.clear_aria_label": "Clear search",
|
|
13
|
+
"empty_state.title": "No {{item_label}} yet",
|
|
14
|
+
"empty_state.description": "Get started by adding your first {{item}}.",
|
|
15
|
+
"empty_state.cta": "Add your first {{item}}",
|
|
16
|
+
"import_export.export": "Export",
|
|
17
|
+
"import_export.export_title": "Export as JSON",
|
|
18
|
+
"import_export.import": "Import",
|
|
19
|
+
"import_export.import_title": "Import from JSON",
|
|
20
|
+
"import_export.error_read_file": "Failed to read file",
|
|
21
|
+
"import_export.error_invalid_json": "Invalid JSON file",
|
|
22
|
+
"import_export.error_no_valid_items": "No valid items found",
|
|
23
|
+
"import_export.added": "Added {{count}}",
|
|
24
|
+
"import_export.skipped": "{{count}} skipped",
|
|
25
|
+
"import_export.invalid": "{{count}} invalid",
|
|
26
|
+
"list_item.select_item_aria": "Select item",
|
|
27
|
+
"list_item.edit_item_aria": "Edit item",
|
|
28
|
+
"list_item.delete_item_aria": "Delete item",
|
|
29
|
+
"list_editor.select_all_aria": "Select all",
|
|
30
|
+
"list_editor.add": "Add",
|
|
31
|
+
"list_editor.max_items_title": "Maximum of {{max}} items reached",
|
|
32
|
+
"list_editor.selected_count": "{{count}} selected",
|
|
33
|
+
"list_editor.clear": "Clear",
|
|
34
|
+
"list_editor.edit_selected": "Edit selected",
|
|
35
|
+
"list_editor.delete_selected": "Delete selected",
|
|
36
|
+
"list_editor.no_items_matching": "No {{item_label}} matching “{{term}}”",
|
|
37
|
+
"list_editor.max_reached_note": "Maximum of {{max}} {{item_label}} reached.",
|
|
38
|
+
"list_editor.delete_confirm_title": "Delete “{{name}}”?",
|
|
39
|
+
"list_editor.bulk_delete_title": "Delete {{count}} selected {{label}}?",
|
|
40
|
+
"list_editor.bulk_delete_description": "This action cannot be undone.",
|
|
41
|
+
"list_editor.bulk_delete_confirm_label": "Delete {{count}}",
|
|
42
|
+
"bulk_edit.title": "Edit {{count}} selected items",
|
|
43
|
+
"bulk_edit.description": "Enable a field below to apply a new value to all selected items. Disabled fields are left unchanged.",
|
|
44
|
+
"bulk_edit.leave_unchanged": "Leave unchanged",
|
|
45
|
+
"bulk_edit.no_fields": "No editable fields configured.",
|
|
46
|
+
"bulk_edit.apply": "Apply to {{count}} items",
|
|
47
|
+
"edit_modal.field_required": "{{label}} is required",
|
|
48
|
+
"edit_modal.id_required": "ID is required",
|
|
49
|
+
"edit_modal.id_duplicate": "This ID already exists",
|
|
50
|
+
"edit_modal.auto_generated_hint": "Auto-generated from {{source}}",
|
|
51
|
+
"edit_modal.title_new": "New {{item_type}}",
|
|
52
|
+
"edit_modal.title_edit": "Edit {{item_type}}",
|
|
53
|
+
"edit_modal.saving": "Saving…",
|
|
54
|
+
"edit_modal.add_button": "Add {{item_type}}",
|
|
55
|
+
"edit_modal.save_changes": "Save Changes",
|
|
56
|
+
"edit_modal.tag_search_placeholder": "Type to search...",
|
|
57
|
+
"edit_modal.no_matching_options": "No matching options",
|
|
58
|
+
"color_swatch.select_aria": "Select color {{color}}",
|
|
59
|
+
"config_editor.title": "Configuration Editor",
|
|
60
|
+
"config_editor.unsaved_suffix": "(unsaved changes)",
|
|
61
|
+
"config_editor.refresh_aria": "Refresh configuration",
|
|
62
|
+
"config_editor.refresh": "Refresh",
|
|
63
|
+
"config_editor.save_aria": "Save configuration",
|
|
64
|
+
"config_editor.save": "Save",
|
|
65
|
+
"config_editor.sections_heading": "Sections",
|
|
66
|
+
"config_editor.sections_aria": "Configuration sections",
|
|
67
|
+
"config_editor.add_new_key_heading": "Add New Key",
|
|
68
|
+
"config_editor.key_name_label": "Key name",
|
|
69
|
+
"config_editor.value_label": "Value",
|
|
70
|
+
"config_editor.add_key": "Add Key",
|
|
71
|
+
"config_editor.select_section_prompt": "Select a section to view/edit",
|
|
72
|
+
"config_editor.delete_key_aria": "Delete {{key}}",
|
|
73
|
+
"config_viewer.empty": "No configuration sections found",
|
|
74
|
+
"config_viewer.save_changes_aria": "Save changes",
|
|
75
|
+
"config_viewer.cancel_editing_aria": "Cancel editing",
|
|
76
|
+
"config_viewer.edit_key_aria": "Edit {{key}}",
|
|
77
|
+
"config_viewer.empty_value": "(empty)",
|
|
78
|
+
"app_config.title_default": "Configuration",
|
|
79
|
+
"app_config.loading": "Loading configuration...",
|
|
80
|
+
"app_config.unsaved_changes": "You have unsaved changes",
|
|
81
|
+
"app_config.refresh": "Refresh",
|
|
82
|
+
"app_config.save": "Save",
|
|
83
|
+
"app_config.saving": "Saving...",
|
|
84
|
+
"app_config.sections_heading": "Sections",
|
|
85
|
+
"app_config.no_sections": "No sections yet. Click + to add your first section.",
|
|
86
|
+
"app_config.add_key": "Add Key",
|
|
87
|
+
"app_config.no_keys": "No keys in this section yet. Click “Add Key” to get started.",
|
|
88
|
+
"app_config.select_section_prompt": "Select a section to view and edit keys",
|
|
89
|
+
"app_config.create_first_section_prompt": "Click + to create your first configuration section",
|
|
90
|
+
"app_config.delete_key_confirm": "Delete key “{{key}}”?",
|
|
91
|
+
"app_config.invalid_json": "Invalid JSON format",
|
|
92
|
+
"app_config.dialog_title_add_section": "Add New Section",
|
|
93
|
+
"app_config.dialog_title_add_key": "Add New Key",
|
|
94
|
+
"app_config.section_name_label": "Section Name",
|
|
95
|
+
"app_config.section_name_placeholder": "e.g., integrations, notifications",
|
|
96
|
+
"app_config.key_name_label": "Key Name",
|
|
97
|
+
"app_config.key_name_placeholder": "e.g., api_key, webhook_url",
|
|
98
|
+
"app_config.type_label": "Type",
|
|
99
|
+
"app_config.type_general": "General (Text)",
|
|
100
|
+
"app_config.type_json": "JSON (Object)",
|
|
101
|
+
"app_config.value_label": "Value",
|
|
102
|
+
"app_config.value_placeholder": "Enter value",
|
|
103
|
+
"app_config.create_section": "Create Section"
|
|
104
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_config",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Config wrapper with error handling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"prebuild": "npm run clean",
|
|
41
43
|
"build": "tsc -p tsconfig.build.json",
|
|
42
44
|
"prepublishOnly": "npm run build",
|
|
43
45
|
"storybook": "npx storybook dev -p 6006",
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
"@types/react": "^18.3.3",
|
|
97
99
|
"@types/react-dom": "^18.3.0",
|
|
98
100
|
"@vitejs/plugin-react": "^4.2.0",
|
|
99
|
-
"hazo_connect": "^4.
|
|
101
|
+
"hazo_connect": "^4.3.2",
|
|
100
102
|
"jsdom": "^24.1.3",
|
|
101
103
|
"postcss": "^8.4.49",
|
|
102
104
|
"react": "^18.2.0",
|
|
@@ -108,10 +110,11 @@
|
|
|
108
110
|
"vitest": "^4.0.15"
|
|
109
111
|
},
|
|
110
112
|
"peerDependencies": {
|
|
111
|
-
"hazo_connect": "^4.
|
|
112
|
-
"hazo_core": "^2.
|
|
113
|
-
"
|
|
114
|
-
"
|
|
113
|
+
"hazo_connect": "^4.3.2",
|
|
114
|
+
"hazo_core": "^2.4.0",
|
|
115
|
+
"hazo_i18n": "^0.9.0",
|
|
116
|
+
"hazo_theme": "^1.4.0",
|
|
117
|
+
"hazo_ui": "^6.14.0",
|
|
115
118
|
"react": "^18.0.0 || ^19.0.0",
|
|
116
119
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
117
120
|
"tailwindcss": "^4.0.0"
|
|
@@ -123,6 +126,9 @@
|
|
|
123
126
|
"hazo_core": {
|
|
124
127
|
"optional": true
|
|
125
128
|
},
|
|
129
|
+
"hazo_i18n": {
|
|
130
|
+
"optional": true
|
|
131
|
+
},
|
|
126
132
|
"tailwindcss": {
|
|
127
133
|
"optional": true
|
|
128
134
|
},
|