@happyvertical/smrt-products 0.30.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/AGENTS.md +122 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +115 -0
- package/dist/lib/__smrt-register__.d.ts +2 -0
- package/dist/lib/__smrt-register__.d.ts.map +1 -0
- package/dist/lib/app/main.d.ts +6 -0
- package/dist/lib/app/main.d.ts.map +1 -0
- package/dist/lib/chunks/ProductAssetCollection-DFPXN43q.js +64 -0
- package/dist/lib/chunks/ProductAssetCollection-DFPXN43q.js.map +1 -0
- package/dist/lib/chunks/ProductForm-DHeb2L24.js +371 -0
- package/dist/lib/chunks/ProductForm-DHeb2L24.js.map +1 -0
- package/dist/lib/chunks/Sku-DUKtbYWT.js +511 -0
- package/dist/lib/chunks/Sku-DUKtbYWT.js.map +1 -0
- package/dist/lib/chunks/SkuCollection-C0tdkEdL.js +160 -0
- package/dist/lib/chunks/SkuCollection-C0tdkEdL.js.map +1 -0
- package/dist/lib/chunks/__smrt-register__-BIgFaVKn.js +5 -0
- package/dist/lib/chunks/__smrt-register__-BIgFaVKn.js.map +1 -0
- package/dist/lib/chunks/index-i3-ci1FB.js +6 -0
- package/dist/lib/chunks/index-i3-ci1FB.js.map +1 -0
- package/dist/lib/chunks/product-store.svelte-Dayd5n3W.js +132 -0
- package/dist/lib/chunks/product-store.svelte-Dayd5n3W.js.map +1 -0
- package/dist/lib/client.d.ts +9 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/collections.d.ts +2 -0
- package/dist/lib/collections.d.ts.map +1 -0
- package/dist/lib/collections.js +12 -0
- package/dist/lib/collections.js.map +1 -0
- package/dist/lib/components.d.ts +2 -0
- package/dist/lib/components.d.ts.map +1 -0
- package/dist/lib/components.js +6 -0
- package/dist/lib/components.js.map +1 -0
- package/dist/lib/generated.d.ts +2 -0
- package/dist/lib/generated.d.ts.map +1 -0
- package/dist/lib/generated.js +5 -0
- package/dist/lib/generated.js.map +1 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +228 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/lib/collections/CategoryCollection.d.ts +20 -0
- package/dist/lib/lib/collections/CategoryCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/MaterialCollection.d.ts +20 -0
- package/dist/lib/lib/collections/MaterialCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/ProductAssetCollection.d.ts +16 -0
- package/dist/lib/lib/collections/ProductAssetCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/ProductCollection.d.ts +12 -0
- package/dist/lib/lib/collections/ProductCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/ProductVariantCollection.d.ts +10 -0
- package/dist/lib/lib/collections/ProductVariantCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/SkuCollection.d.ts +31 -0
- package/dist/lib/lib/collections/SkuCollection.d.ts.map +1 -0
- package/dist/lib/lib/collections/index.d.ts +7 -0
- package/dist/lib/lib/collections/index.d.ts.map +1 -0
- package/dist/lib/lib/components/ProductCard.svelte +173 -0
- package/dist/lib/lib/components/ProductCard.svelte.d.ts +10 -0
- package/dist/lib/lib/components/ProductCard.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/ProductForm.svelte +289 -0
- package/dist/lib/lib/components/ProductForm.svelte.d.ts +11 -0
- package/dist/lib/lib/components/ProductForm.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/TestComponent.svelte +25 -0
- package/dist/lib/lib/components/TestComponent.svelte.d.ts +7 -0
- package/dist/lib/lib/components/TestComponent.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte +240 -0
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte.d.ts +13 -0
- package/dist/lib/lib/components/auto-generated/AutoForm.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte +205 -0
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte.d.ts +14 -0
- package/dist/lib/lib/components/auto-generated/FieldRenderer.svelte.d.ts.map +1 -0
- package/dist/lib/lib/components/index.d.ts +12 -0
- package/dist/lib/lib/components/index.d.ts.map +1 -0
- package/dist/lib/lib/components/index.js +11 -0
- package/dist/lib/lib/features/CategoryManager.svelte +80 -0
- package/dist/lib/lib/features/CategoryManager.svelte.d.ts +7 -0
- package/dist/lib/lib/features/CategoryManager.svelte.d.ts.map +1 -0
- package/dist/lib/lib/features/ProductCatalog.svelte +299 -0
- package/dist/lib/lib/features/ProductCatalog.svelte.d.ts +8 -0
- package/dist/lib/lib/features/ProductCatalog.svelte.d.ts.map +1 -0
- package/dist/lib/lib/federation-entry.d.ts +11 -0
- package/dist/lib/lib/federation-entry.d.ts.map +1 -0
- package/dist/lib/lib/generated/index.d.ts +2 -0
- package/dist/lib/lib/generated/index.d.ts.map +1 -0
- package/dist/lib/lib/i18n.d.ts +79 -0
- package/dist/lib/lib/i18n.d.ts.map +1 -0
- package/dist/lib/lib/index.d.ts +20 -0
- package/dist/lib/lib/index.d.ts.map +1 -0
- package/dist/lib/lib/mock-smrt-client.d.ts +40 -0
- package/dist/lib/lib/mock-smrt-client.d.ts.map +1 -0
- package/dist/lib/lib/mock-smrt-client.js +129 -0
- package/dist/lib/lib/mock-smrt-client.js.map +1 -0
- package/dist/lib/lib/models/Category.d.ts +38 -0
- package/dist/lib/lib/models/Category.d.ts.map +1 -0
- package/dist/lib/lib/models/Material.d.ts +22 -0
- package/dist/lib/lib/models/Material.d.ts.map +1 -0
- package/dist/lib/lib/models/Product.d.ts +57 -0
- package/dist/lib/lib/models/Product.d.ts.map +1 -0
- package/dist/lib/lib/models/ProductAsset.d.ts +17 -0
- package/dist/lib/lib/models/ProductAsset.d.ts.map +1 -0
- package/dist/lib/lib/models/ProductVariant.d.ts +51 -0
- package/dist/lib/lib/models/ProductVariant.d.ts.map +1 -0
- package/dist/lib/lib/models/Sku.d.ts +79 -0
- package/dist/lib/lib/models/Sku.d.ts.map +1 -0
- package/dist/lib/lib/models/index.d.ts +15 -0
- package/dist/lib/lib/models/index.d.ts.map +1 -0
- package/dist/lib/lib/models/types.d.ts +41 -0
- package/dist/lib/lib/models/types.d.ts.map +1 -0
- package/dist/lib/lib/stores/index.d.ts +8 -0
- package/dist/lib/lib/stores/index.d.ts.map +1 -0
- package/dist/lib/lib/stores/index.js +7 -0
- package/dist/lib/lib/stores/product-store.client.svelte.d.ts +45 -0
- package/dist/lib/lib/stores/product-store.client.svelte.d.ts.map +1 -0
- package/dist/lib/lib/stores/product-store.client.svelte.js +147 -0
- package/dist/lib/lib/stores/product-store.svelte.d.ts +29 -0
- package/dist/lib/lib/stores/product-store.svelte.d.ts.map +1 -0
- package/dist/lib/lib/stores/product-store.svelte.js +144 -0
- package/dist/lib/lib/types.d.ts +43 -0
- package/dist/lib/lib/types.d.ts.map +1 -0
- package/dist/lib/lib/utils/index.d.ts +10 -0
- package/dist/lib/lib/utils/index.d.ts.map +1 -0
- package/dist/lib/main.d.ts +5 -0
- package/dist/lib/main.d.ts.map +1 -0
- package/dist/lib/manifest.json +3758 -0
- package/dist/lib/mcp.d.ts +14 -0
- package/dist/lib/mcp.d.ts.map +1 -0
- package/dist/lib/models.d.ts +2 -0
- package/dist/lib/models.d.ts.map +1 -0
- package/dist/lib/models.js +12 -0
- package/dist/lib/models.js.map +1 -0
- package/dist/lib/native-api-server.d.ts +7 -0
- package/dist/lib/native-api-server.d.ts.map +1 -0
- package/dist/lib/server.d.ts +11 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/simple-api-server.d.ts +7 -0
- package/dist/lib/simple-api-server.d.ts.map +1 -0
- package/dist/lib/simple-server.d.ts +6 -0
- package/dist/lib/simple-server.d.ts.map +1 -0
- package/dist/lib/smrt-knowledge.json +1584 -0
- package/dist/lib/smrt-products.css +233 -0
- package/dist/lib/stores.d.ts +2 -0
- package/dist/lib/stores.d.ts.map +1 -0
- package/dist/lib/stores.js +6 -0
- package/dist/lib/stores.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +27 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +127 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Automatic field renderer that maps TypeScript types to UI components
|
|
4
|
+
* This demonstrates the "Define Once, Consume Everywhere" vision
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
8
|
+
import { M } from '../../i18n.js';
|
|
9
|
+
|
|
10
|
+
const { t } = useI18n();
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
fieldName: string;
|
|
14
|
+
fieldType: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
15
|
+
value: any;
|
|
16
|
+
label?: string;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
readonly?: boolean;
|
|
20
|
+
onUpdate?: (value: any) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
fieldName,
|
|
25
|
+
fieldType,
|
|
26
|
+
value = '',
|
|
27
|
+
label,
|
|
28
|
+
placeholder,
|
|
29
|
+
required = false,
|
|
30
|
+
readonly = false,
|
|
31
|
+
onUpdate,
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
// Auto-generate label from field name if not provided
|
|
35
|
+
const _displayLabel =
|
|
36
|
+
label ||
|
|
37
|
+
fieldName
|
|
38
|
+
.replace(/([A-Z])/g, ' $1')
|
|
39
|
+
.replace(/^./, (str) => str.toUpperCase());
|
|
40
|
+
const _fieldId = `field-${fieldName}`;
|
|
41
|
+
|
|
42
|
+
function handleUpdate(newValue: any) {
|
|
43
|
+
if (onUpdate && !readonly) {
|
|
44
|
+
onUpdate(newValue);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _handleStringInput(event: Event) {
|
|
49
|
+
const target = event.target as HTMLInputElement;
|
|
50
|
+
handleUpdate(target.value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function _handleNumberInput(event: Event) {
|
|
54
|
+
const target = event.target as HTMLInputElement;
|
|
55
|
+
handleUpdate(Number.parseFloat(target.value) || 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _handleBooleanInput(event: Event) {
|
|
59
|
+
const target = event.target as HTMLInputElement;
|
|
60
|
+
handleUpdate(target.checked);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _handleArrayInput(event: Event) {
|
|
64
|
+
const target = event.target as HTMLTextAreaElement;
|
|
65
|
+
try {
|
|
66
|
+
// Simple array handling - comma separated values
|
|
67
|
+
const arrayValue = target.value
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((s) => s.trim())
|
|
70
|
+
.filter((s) => s);
|
|
71
|
+
handleUpdate(arrayValue);
|
|
72
|
+
} catch {
|
|
73
|
+
// Keep current value on parse error
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _handleObjectInput(event: Event) {
|
|
78
|
+
const target = event.target as HTMLTextAreaElement;
|
|
79
|
+
try {
|
|
80
|
+
const objectValue = JSON.parse(target.value);
|
|
81
|
+
handleUpdate(objectValue);
|
|
82
|
+
} catch {
|
|
83
|
+
// Keep current value on parse error
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<div class="field-renderer">
|
|
89
|
+
<label for={fieldId} class="field-label">
|
|
90
|
+
{displayLabel}
|
|
91
|
+
{#if required}<span class="required">*</span>{/if}
|
|
92
|
+
</label>
|
|
93
|
+
|
|
94
|
+
{#if fieldType === 'string'}
|
|
95
|
+
<input
|
|
96
|
+
id={fieldId}
|
|
97
|
+
type="text"
|
|
98
|
+
class="field-input"
|
|
99
|
+
{value}
|
|
100
|
+
{placeholder}
|
|
101
|
+
{readonly}
|
|
102
|
+
{required}
|
|
103
|
+
oninput={handleStringInput}
|
|
104
|
+
/>
|
|
105
|
+
{:else if fieldType === 'number'}
|
|
106
|
+
<input
|
|
107
|
+
id={fieldId}
|
|
108
|
+
type="number"
|
|
109
|
+
class="field-input"
|
|
110
|
+
value={value || 0}
|
|
111
|
+
{placeholder}
|
|
112
|
+
{readonly}
|
|
113
|
+
{required}
|
|
114
|
+
oninput={handleNumberInput}
|
|
115
|
+
/>
|
|
116
|
+
{:else if fieldType === 'boolean'}
|
|
117
|
+
<input
|
|
118
|
+
id={fieldId}
|
|
119
|
+
type="checkbox"
|
|
120
|
+
class="field-checkbox"
|
|
121
|
+
checked={value || false}
|
|
122
|
+
{readonly}
|
|
123
|
+
onchange={handleBooleanInput}
|
|
124
|
+
/>
|
|
125
|
+
{:else if fieldType === 'array'}
|
|
126
|
+
<textarea
|
|
127
|
+
id={fieldId}
|
|
128
|
+
class="field-textarea"
|
|
129
|
+
value={Array.isArray(value) ? value.join(', ') : ''}
|
|
130
|
+
placeholder={placeholder || 'Enter comma-separated values'}
|
|
131
|
+
{readonly}
|
|
132
|
+
{required}
|
|
133
|
+
oninput={handleArrayInput}
|
|
134
|
+
/>
|
|
135
|
+
<div class="field-hint">{t(M['products.field_renderer.array_hint'])}</div>
|
|
136
|
+
{:else if fieldType === 'object'}
|
|
137
|
+
<textarea
|
|
138
|
+
id={fieldId}
|
|
139
|
+
class="field-textarea"
|
|
140
|
+
value={typeof value === 'object' ? JSON.stringify(value, null, 2) : '{}'}
|
|
141
|
+
placeholder={placeholder || 'Enter JSON object'}
|
|
142
|
+
{readonly}
|
|
143
|
+
{required}
|
|
144
|
+
oninput={handleObjectInput}
|
|
145
|
+
/>
|
|
146
|
+
<div class="field-hint">{t(M['products.field_renderer.object_hint'])}</div>
|
|
147
|
+
{/if}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<style>
|
|
151
|
+
.field-renderer {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 0.5rem;
|
|
155
|
+
margin-bottom: 1rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.field-label {
|
|
159
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
160
|
+
color: var(--smrt-color-on-surface, #374151);
|
|
161
|
+
font-size: var(--smrt-typography-label-large-size, 0.875rem);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.required {
|
|
165
|
+
color: var(--smrt-color-error, #dc2626);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.field-input,
|
|
169
|
+
.field-textarea {
|
|
170
|
+
padding: 0.5rem 0.75rem;
|
|
171
|
+
border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
|
|
172
|
+
border-radius: 0.375rem;
|
|
173
|
+
font-size: var(--smrt-typography-body-medium-size, 0.875rem);
|
|
174
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.field-input:focus,
|
|
178
|
+
.field-textarea:focus {
|
|
179
|
+
outline: none;
|
|
180
|
+
border-color: var(--smrt-color-primary, #3b82f6);
|
|
181
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary, #3b82f6) 10%, transparent);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.field-textarea {
|
|
185
|
+
min-height: 4rem;
|
|
186
|
+
resize: vertical;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.field-checkbox {
|
|
190
|
+
width: 1rem;
|
|
191
|
+
height: 1rem;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.field-hint {
|
|
195
|
+
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
196
|
+
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
197
|
+
font-style: italic;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.field-input:read-only,
|
|
201
|
+
.field-textarea:read-only {
|
|
202
|
+
background-color: var(--smrt-color-surface-container-low, #f9fafb);
|
|
203
|
+
cursor: not-allowed;
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
fieldName: string;
|
|
3
|
+
fieldType: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
4
|
+
value: any;
|
|
5
|
+
label?: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
readonly?: boolean;
|
|
9
|
+
onUpdate?: (value: any) => void;
|
|
10
|
+
}
|
|
11
|
+
declare const FieldRenderer: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type FieldRenderer = ReturnType<typeof FieldRenderer>;
|
|
13
|
+
export default FieldRenderer;
|
|
14
|
+
//# sourceMappingURL=FieldRenderer.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldRenderer.svelte.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/auto-generated/FieldRenderer.svelte.ts"],"names":[],"mappings":"AAWA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IAChE,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CACjC;AAmGD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMRT Template UI Components
|
|
3
|
+
*
|
|
4
|
+
* Reusable UI components for product management.
|
|
5
|
+
* These components work with SMRT-generated types and can be:
|
|
6
|
+
* - Imported as NPM package components
|
|
7
|
+
* - Consumed via module federation
|
|
8
|
+
* - Used in the standalone application
|
|
9
|
+
*/
|
|
10
|
+
export { default as ProductCard } from './ProductCard.svelte';
|
|
11
|
+
export { default as ProductForm } from './ProductForm.svelte';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMRT Template UI Components
|
|
3
|
+
*
|
|
4
|
+
* Reusable UI components for product management.
|
|
5
|
+
* These components work with SMRT-generated types and can be:
|
|
6
|
+
* - Imported as NPM package components
|
|
7
|
+
* - Consumed via module federation
|
|
8
|
+
* - Used in the standalone application
|
|
9
|
+
*/
|
|
10
|
+
export { default as ProductCard } from './ProductCard.svelte';
|
|
11
|
+
export { default as ProductForm } from './ProductForm.svelte';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
+
import { M } from '../i18n.js';
|
|
4
|
+
import type { CategoryData } from '../types';
|
|
5
|
+
|
|
6
|
+
const { t } = useI18n();
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
readonly?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { readonly = false }: Props = $props();
|
|
13
|
+
|
|
14
|
+
// Placeholder for category management
|
|
15
|
+
const _categories = $state<CategoryData[]>([]);
|
|
16
|
+
const _loading = $state(false);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<div class="category-manager">
|
|
20
|
+
<div class="manager-header">
|
|
21
|
+
<h2>{t(M['products.category_manager.title'])}</h2>
|
|
22
|
+
<p>{t(M['products.category_manager.subtitle'])}</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="placeholder-content">
|
|
26
|
+
<p>{t(M['products.category_manager.coming_soon'])}</p>
|
|
27
|
+
<p>{t(M['products.category_manager.will_include'])}</p>
|
|
28
|
+
<ul>
|
|
29
|
+
<li>{t(M['products.category_manager.create_edit'])}</li>
|
|
30
|
+
<li>{t(M['products.category_manager.organize_hierarchy'])}</li>
|
|
31
|
+
<li>{t(M['products.category_manager.manage_permissions'])}</li>
|
|
32
|
+
<li>{t(M['products.category_manager.analytics'])}</li>
|
|
33
|
+
</ul>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
.category-manager {
|
|
39
|
+
max-width: 800px;
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
padding: 1rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.manager-header {
|
|
45
|
+
text-align: center;
|
|
46
|
+
margin-bottom: 2rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.manager-header h2 {
|
|
50
|
+
margin: 0 0 0.5rem 0;
|
|
51
|
+
color: var(--smrt-color-on-surface, #1f2937);
|
|
52
|
+
font-size: var(--smrt-typography-headline-small-size, 1.5rem);
|
|
53
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.manager-header p {
|
|
57
|
+
margin: 0;
|
|
58
|
+
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.placeholder-content {
|
|
62
|
+
background: var(--smrt-color-surface-container-low, #f9fafb);
|
|
63
|
+
border: 1px solid var(--smrt-color-outline-variant, #e2e8f0);
|
|
64
|
+
border-radius: var(--smrt-radius-md, 8px);
|
|
65
|
+
padding: 2rem;
|
|
66
|
+
text-align: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.placeholder-content p {
|
|
70
|
+
margin: 0 0 1rem 0;
|
|
71
|
+
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.placeholder-content ul {
|
|
75
|
+
margin: 1rem 0 0 0;
|
|
76
|
+
text-align: left;
|
|
77
|
+
display: inline-block;
|
|
78
|
+
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
readonly?: boolean;
|
|
3
|
+
}
|
|
4
|
+
declare const CategoryManager: import("svelte").Component<Props, {}, "">;
|
|
5
|
+
type CategoryManager = ReturnType<typeof CategoryManager>;
|
|
6
|
+
export default CategoryManager;
|
|
7
|
+
//# sourceMappingURL=CategoryManager.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryManager.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/features/CategoryManager.svelte"],"names":[],"mappings":"AAQA,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqCD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
import { productStore } from '../stores/product-store.svelte';
|
|
5
|
+
import { M } from '../i18n.js';
|
|
6
|
+
import type { ProductData } from '../types';
|
|
7
|
+
|
|
8
|
+
const { t } = useI18n();
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
readonly?: boolean;
|
|
12
|
+
showCreateForm?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { readonly = false, showCreateForm = false }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const searchQuery = $state('');
|
|
18
|
+
const selectedCategory = $state('');
|
|
19
|
+
let _showForm = $state(false);
|
|
20
|
+
let editingProduct = $state<ProductData | null>(null);
|
|
21
|
+
|
|
22
|
+
// Reactive filtered products
|
|
23
|
+
const _filteredProducts = $derived.by(() => {
|
|
24
|
+
let products = productStore.items;
|
|
25
|
+
|
|
26
|
+
if (searchQuery) {
|
|
27
|
+
products = productStore.searchProducts(searchQuery);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (selectedCategory) {
|
|
31
|
+
products = products.filter((p) => p.category === selectedCategory);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return products;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
onMount(() => {
|
|
38
|
+
productStore.loadProducts();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function _handleCreateProduct() {
|
|
42
|
+
editingProduct = null;
|
|
43
|
+
_showForm = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _handleEditProduct(product: ProductData) {
|
|
47
|
+
editingProduct = product;
|
|
48
|
+
_showForm = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function _handleDeleteProduct(id: string) {
|
|
52
|
+
if (confirm('Are you sure you want to delete this product?')) {
|
|
53
|
+
try {
|
|
54
|
+
await productStore.deleteProduct(id);
|
|
55
|
+
} catch (error) {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function _handleSubmitProduct(productData: Partial<ProductData>) {
|
|
60
|
+
try {
|
|
61
|
+
if (editingProduct?.id) {
|
|
62
|
+
await productStore.updateProduct(editingProduct.id, productData);
|
|
63
|
+
} else {
|
|
64
|
+
await productStore.createProduct(productData);
|
|
65
|
+
}
|
|
66
|
+
_showForm = false;
|
|
67
|
+
editingProduct = null;
|
|
68
|
+
} catch (error) {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function _handleCancelForm() {
|
|
72
|
+
_showForm = false;
|
|
73
|
+
editingProduct = null;
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<div class="product-catalog">
|
|
78
|
+
<div class="catalog-header">
|
|
79
|
+
<h2>{t(M['products.product_catalog.title'])}</h2>
|
|
80
|
+
|
|
81
|
+
<div class="catalog-stats">
|
|
82
|
+
<span class="stat">
|
|
83
|
+
<strong>{productStore.items.length}</strong> products
|
|
84
|
+
</span>
|
|
85
|
+
<span class="stat">
|
|
86
|
+
<strong>{productStore.inStockCount}</strong> {t(M['products.product_catalog.in_stock'])}
|
|
87
|
+
</span>
|
|
88
|
+
<span class="stat">
|
|
89
|
+
{t(M['products.product_catalog.total_value'])} <strong>${productStore.totalValue.toFixed(2)}</strong>
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="catalog-controls">
|
|
95
|
+
<div class="search-filters">
|
|
96
|
+
<input
|
|
97
|
+
type="text"
|
|
98
|
+
bind:value={searchQuery}
|
|
99
|
+
placeholder={t(M['products.product_catalog.search_placeholder'])}
|
|
100
|
+
class="search-input"
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
<select bind:value={selectedCategory} class="category-filter">
|
|
104
|
+
<option value="">{t(M['products.product_catalog.all_categories'])}</option>
|
|
105
|
+
{#each productStore.categories as category}
|
|
106
|
+
<option value={category}>{category}</option>
|
|
107
|
+
{/each}
|
|
108
|
+
</select>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{#if !readonly && (showCreateForm || productStore.items.length === 0)}
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
onclick={handleCreateProduct}
|
|
115
|
+
class="create-btn"
|
|
116
|
+
>
|
|
117
|
+
{t(M['products.product_catalog.add_product'])}
|
|
118
|
+
</button>
|
|
119
|
+
{/if}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{#if productStore.loading}
|
|
123
|
+
<div class="loading-state">
|
|
124
|
+
<p>{t(M['products.product_catalog.loading'])}</p>
|
|
125
|
+
</div>
|
|
126
|
+
{:else if productStore.error}
|
|
127
|
+
<div class="error-state">
|
|
128
|
+
<p>Error: {productStore.error}</p>
|
|
129
|
+
<button type="button" onclick={() => productStore.loadProducts()}>
|
|
130
|
+
Retry
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
{:else if filteredProducts.length === 0}
|
|
134
|
+
<div class="empty-state">
|
|
135
|
+
{#if productStore.items.length === 0}
|
|
136
|
+
<p>{t(M['products.product_catalog.empty'])}</p>
|
|
137
|
+
{#if !readonly}
|
|
138
|
+
<button type="button" onclick={handleCreateProduct} class="create-btn">
|
|
139
|
+
{t(M['products.product_catalog.create_first'])}
|
|
140
|
+
</button>
|
|
141
|
+
{/if}
|
|
142
|
+
{:else}
|
|
143
|
+
<p>{t(M['products.product_catalog.no_match'])}</p>
|
|
144
|
+
{/if}
|
|
145
|
+
</div>
|
|
146
|
+
{:else}
|
|
147
|
+
<div class="products-grid">
|
|
148
|
+
{#each filteredProducts as product (product.id)}
|
|
149
|
+
<ProductCard
|
|
150
|
+
{product}
|
|
151
|
+
onEdit={readonly ? undefined : handleEditProduct}
|
|
152
|
+
onDelete={readonly ? undefined : handleDeleteProduct}
|
|
153
|
+
/>
|
|
154
|
+
{/each}
|
|
155
|
+
</div>
|
|
156
|
+
{/if}
|
|
157
|
+
|
|
158
|
+
{#if showForm && !readonly}
|
|
159
|
+
<div class="form-overlay">
|
|
160
|
+
<div class="form-container">
|
|
161
|
+
<h3>{editingProduct ? 'Edit Product' : 'Create New Product'}</h3>
|
|
162
|
+
<ProductForm
|
|
163
|
+
product={editingProduct}
|
|
164
|
+
onSubmit={handleSubmitProduct}
|
|
165
|
+
onCancel={handleCancelForm}
|
|
166
|
+
loading={productStore.loading}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
{/if}
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<style>
|
|
174
|
+
.product-catalog {
|
|
175
|
+
max-width: 1200px;
|
|
176
|
+
margin: 0 auto;
|
|
177
|
+
padding: 1rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.catalog-header {
|
|
181
|
+
display: flex;
|
|
182
|
+
justify-content: space-between;
|
|
183
|
+
align-items: center;
|
|
184
|
+
margin-bottom: 1.5rem;
|
|
185
|
+
padding-bottom: 1rem;
|
|
186
|
+
border-bottom: 2px solid var(--smrt-color-outline-variant, #e2e8f0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.catalog-header h2 {
|
|
190
|
+
margin: 0;
|
|
191
|
+
color: var(--smrt-color-on-surface, #1f2937);
|
|
192
|
+
font-size: var(--smrt-typography-headline-large-size, 1.875rem);
|
|
193
|
+
font-weight: var(--smrt-typography-weight-bold, 700);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.catalog-stats {
|
|
197
|
+
display: flex;
|
|
198
|
+
gap: 1.5rem;
|
|
199
|
+
font-size: var(--smrt-typography-label-large-size, 0.875rem);
|
|
200
|
+
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.catalog-controls {
|
|
204
|
+
display: flex;
|
|
205
|
+
justify-content: space-between;
|
|
206
|
+
align-items: center;
|
|
207
|
+
margin-bottom: 1.5rem;
|
|
208
|
+
gap: 1rem;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.search-filters {
|
|
212
|
+
display: flex;
|
|
213
|
+
gap: 0.75rem;
|
|
214
|
+
flex: 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.search-input, .category-filter {
|
|
218
|
+
padding: 0.5rem;
|
|
219
|
+
border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
|
|
220
|
+
border-radius: var(--smrt-radius-sm, 4px);
|
|
221
|
+
font-size: var(--smrt-typography-body-medium-size, 0.875rem);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.search-input {
|
|
225
|
+
flex: 1;
|
|
226
|
+
max-width: 300px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.category-filter {
|
|
230
|
+
min-width: 150px;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.create-btn {
|
|
234
|
+
background: var(--smrt-color-primary, #3b82f6);
|
|
235
|
+
color: var(--smrt-color-on-primary, white);
|
|
236
|
+
border: none;
|
|
237
|
+
padding: 0.5rem 1rem;
|
|
238
|
+
border-radius: var(--smrt-radius-sm, 4px);
|
|
239
|
+
font-weight: var(--smrt-typography-weight-medium, 500);
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
transition: background-color 0.2s;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.create-btn:hover {
|
|
245
|
+
background: var(--smrt-color-primary, #2563eb);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.products-grid {
|
|
249
|
+
display: grid;
|
|
250
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
251
|
+
gap: 1.5rem;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.loading-state, .error-state, .empty-state {
|
|
255
|
+
text-align: center;
|
|
256
|
+
padding: 3rem 1rem;
|
|
257
|
+
color: var(--smrt-color-on-surface-variant, #6b7280);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.error-state button {
|
|
261
|
+
margin-top: 0.5rem;
|
|
262
|
+
background: var(--smrt-color-error, #dc2626);
|
|
263
|
+
color: var(--smrt-color-on-error, white);
|
|
264
|
+
border: none;
|
|
265
|
+
padding: 0.5rem 1rem;
|
|
266
|
+
border-radius: var(--smrt-radius-sm, 4px);
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.form-overlay {
|
|
271
|
+
position: fixed;
|
|
272
|
+
top: 0;
|
|
273
|
+
left: 0;
|
|
274
|
+
right: 0;
|
|
275
|
+
bottom: 0;
|
|
276
|
+
background: var(--smrt-color-scrim, rgba(0, 0, 0, 0.5));
|
|
277
|
+
display: flex;
|
|
278
|
+
align-items: center;
|
|
279
|
+
justify-content: center;
|
|
280
|
+
z-index: var(--smrt-z-index-dialog, 1300);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.form-container {
|
|
284
|
+
background: white;
|
|
285
|
+
border-radius: var(--smrt-radius-md, 8px);
|
|
286
|
+
max-width: 500px;
|
|
287
|
+
width: 90vw;
|
|
288
|
+
max-height: 90vh;
|
|
289
|
+
overflow-y: auto;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.form-container h3 {
|
|
293
|
+
margin: 0 0 1rem 0;
|
|
294
|
+
padding: 1.5rem 1.5rem 0 1.5rem;
|
|
295
|
+
color: var(--smrt-color-on-surface, #1f2937);
|
|
296
|
+
font-size: var(--smrt-typography-title-large-size, 1.25rem);
|
|
297
|
+
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
298
|
+
}
|
|
299
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
readonly?: boolean;
|
|
3
|
+
showCreateForm?: boolean;
|
|
4
|
+
}
|
|
5
|
+
declare const ProductCatalog: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type ProductCatalog = ReturnType<typeof ProductCatalog>;
|
|
7
|
+
export default ProductCatalog;
|
|
8
|
+
//# sourceMappingURL=ProductCatalog.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductCatalog.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/features/ProductCatalog.svelte"],"names":[],"mappings":"AAUA,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA0JD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federation Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This file provides clean, browser-compatible exports for module federation
|
|
5
|
+
* without any server-side dependencies or SMRT virtual modules.
|
|
6
|
+
*/
|
|
7
|
+
export { default as ProductCard } from './components/ProductCard.svelte';
|
|
8
|
+
export { default as ProductForm } from './components/ProductForm.svelte';
|
|
9
|
+
export { default as CategoryManager } from './features/CategoryManager.svelte';
|
|
10
|
+
export type { CategoryData, ProductData } from './types';
|
|
11
|
+
//# sourceMappingURL=federation-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federation-entry.d.ts","sourceRoot":"","sources":["../../../src/lib/federation-entry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAG/E,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/generated/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,4BAA4B,CAAC;AASpC,eAAO,MAAM,uBAAuB,IAAK,CAAC"}
|