@postxl/generators 1.11.6 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/backend-view/model-view-service.generator.js +4 -1
  2. package/dist/backend-view/model-view-service.generator.js.map +1 -1
  3. package/dist/backend-view/template/query.utils.test.ts +34 -0
  4. package/dist/backend-view/template/query.utils.ts +89 -0
  5. package/dist/frontend-admin/admin.generator.d.ts +9 -0
  6. package/dist/frontend-admin/admin.generator.js +19 -0
  7. package/dist/frontend-admin/admin.generator.js.map +1 -1
  8. package/dist/frontend-admin/generators/admin-sidebar.generator.js +1 -1
  9. package/dist/frontend-admin/generators/audit-log-sidebar.generator.js +107 -113
  10. package/dist/frontend-admin/generators/audit-log-sidebar.generator.js.map +1 -1
  11. package/dist/frontend-admin/generators/comment-sidebar.generator.d.ts +9 -0
  12. package/dist/frontend-admin/generators/comment-sidebar.generator.js +246 -0
  13. package/dist/frontend-admin/generators/comment-sidebar.generator.js.map +1 -0
  14. package/dist/frontend-admin/generators/detail-sidebar.generator.d.ts +9 -0
  15. package/dist/frontend-admin/generators/detail-sidebar.generator.js +148 -0
  16. package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -0
  17. package/dist/frontend-admin/generators/model-admin-page.generator.js +40 -6
  18. package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
  19. package/dist/frontend-core/frontend.generator.js +2 -1
  20. package/dist/frontend-core/frontend.generator.js.map +1 -1
  21. package/dist/frontend-core/template/docs/LOGIN_PROCESS.d2 +372 -0
  22. package/dist/frontend-core/template/docs/LOGIN_PROCESS.md +214 -0
  23. package/dist/frontend-core/template/docs/LOGIN_PROCESS.svg +914 -0
  24. package/dist/frontend-core/template/src/components/admin/table-filter.stories.tsx +265 -0
  25. package/dist/frontend-core/template/src/components/admin/table-filter.tsx +224 -128
  26. package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +159 -0
  27. package/dist/frontend-core/template/src/context-providers/auth-context-provider.tsx +54 -43
  28. package/dist/frontend-core/template/src/hooks/use-table-view-config.tsx +249 -0
  29. package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +5 -5
  30. package/dist/frontend-core/template/src/pages/error/auth-error.page.tsx +37 -0
  31. package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +24 -40
  32. package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +8 -10
  33. package/dist/frontend-core/template/src/pages/login/login.page.tsx +39 -13
  34. package/dist/frontend-core/template/src/pages/unauthorized/unauthorized.page.tsx +2 -2
  35. package/dist/frontend-core/template/src/routes/_auth-routes.tsx +21 -8
  36. package/dist/frontend-core/template/src/routes/auth-error.tsx +19 -0
  37. package/dist/frontend-core/template/vite.config.ts +5 -0
  38. package/dist/frontend-core/types/hook.d.ts +1 -1
  39. package/dist/frontend-tables/generators/model-table.generator.js +30 -5
  40. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  41. package/dist/types/template/query.types.ts +19 -1
  42. package/package.json +5 -5
@@ -0,0 +1,265 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
3
+ import { useState } from 'react'
4
+
5
+ import { FilterConfig, FilterFieldName, FilterOption, FilterState, FilterValue } from '@types'
6
+
7
+ import { Popover, PopoverContent, PopoverTrigger, Button } from '@postxl/ui-components'
8
+
9
+ import { TableFilter } from './table-filter'
10
+
11
+ const meta = {
12
+ title: 'Admin/TableFilter',
13
+ component: TableFilter,
14
+ tags: ['autodocs'],
15
+ parameters: {
16
+ layout: 'centered',
17
+ },
18
+ decorators: [
19
+ (Story) => {
20
+ const queryClient = new QueryClient({
21
+ defaultOptions: {
22
+ queries: {
23
+ retry: false,
24
+ },
25
+ },
26
+ })
27
+ return (
28
+ <QueryClientProvider client={queryClient}>
29
+ <Story />
30
+ </QueryClientProvider>
31
+ )
32
+ },
33
+ ],
34
+ } satisfies Meta<typeof TableFilter>
35
+ export default meta
36
+
37
+ type Story = StoryObj<typeof meta>
38
+
39
+ // Mock data for different filter types
40
+ const stringOptions = [
41
+ { value: '', label: 'Empty', hasMatches: true },
42
+ { value: 'apple', label: 'Apple', hasMatches: true },
43
+ { value: 'banana', label: 'Banana', hasMatches: true },
44
+ { value: 'cherry', label: 'Cherry', hasMatches: true },
45
+ { value: 'date', label: 'Date', hasMatches: true },
46
+ { value: 'elderberry', label: 'Elderberry', hasMatches: true },
47
+ ]
48
+
49
+ const numberOptions = [
50
+ { value: '10', label: '10', hasMatches: true },
51
+ { value: '20', label: '20', hasMatches: true },
52
+ { value: '30', label: '30', hasMatches: true },
53
+ { value: '40', label: '40', hasMatches: true },
54
+ { value: '50', label: '50', hasMatches: true },
55
+ ]
56
+
57
+ const booleanOptions = [
58
+ { value: 'true', label: 'Yes', hasMatches: true },
59
+ { value: 'false', label: 'No', hasMatches: true },
60
+ ]
61
+
62
+ const dateOptions = [
63
+ { value: '2024-01-15', label: '2024-01-15', hasMatches: true },
64
+ { value: '2024-02-20', label: '2024-02-20', hasMatches: true },
65
+ { value: '2024-03-25', label: '2024-03-25', hasMatches: true },
66
+ ]
67
+
68
+ const hierarchyOptions: FilterOption[] = [
69
+ {
70
+ value: 'europe',
71
+ label: 'Europe',
72
+ hasMatches: true,
73
+ children: [
74
+ {
75
+ value: 'germany',
76
+ label: 'Germany',
77
+ hasMatches: true,
78
+ children: [
79
+ { value: 'berlin', label: 'Berlin', hasMatches: true },
80
+ { value: 'munich', label: 'Munich', hasMatches: true },
81
+ { value: 'hamburg', label: 'Hamburg', hasMatches: true },
82
+ ],
83
+ },
84
+ {
85
+ value: 'france',
86
+ label: 'France',
87
+ hasMatches: true,
88
+ children: [
89
+ { value: 'paris', label: 'Paris', hasMatches: true },
90
+ { value: 'lyon', label: 'Lyon', hasMatches: true },
91
+ ],
92
+ },
93
+ {
94
+ value: 'spain',
95
+ label: 'Spain',
96
+ hasMatches: true,
97
+ children: [
98
+ { value: 'madrid', label: 'Madrid', hasMatches: true },
99
+ { value: 'barcelona', label: 'Barcelona', hasMatches: true },
100
+ ],
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ value: 'asia',
106
+ label: 'Asia',
107
+ hasMatches: true,
108
+ children: [
109
+ {
110
+ value: 'japan',
111
+ label: 'Japan',
112
+ hasMatches: true,
113
+ children: [
114
+ { value: 'tokyo', label: 'Tokyo', hasMatches: true },
115
+ { value: 'osaka', label: 'Osaka', hasMatches: true },
116
+ ],
117
+ },
118
+ { value: 'singapore', label: 'Singapore', hasMatches: true },
119
+ ],
120
+ },
121
+ ]
122
+
123
+ // Mock filter configs
124
+ type MockFilterState = FilterState & {
125
+ stringField?: { values?: string[] }
126
+ numberField?: { values?: number[]; min?: number; max?: number }
127
+ booleanField?: boolean[]
128
+ dateField?: { values?: string[]; start?: string; end?: string }
129
+ hierarchyField?: { values?: string[] }
130
+ }
131
+
132
+ const stringFilterConfig: FilterConfig<FilterFieldName, MockFilterState> = {
133
+ stringField: {
134
+ filterKey: 'stringField',
135
+ valueType: 'string',
136
+ kind: 'scalar',
137
+ },
138
+ }
139
+
140
+ const numberFilterConfig: FilterConfig<FilterFieldName, MockFilterState> = {
141
+ numberField: {
142
+ filterKey: 'numberField',
143
+ valueType: 'number',
144
+ kind: 'scalar',
145
+ },
146
+ }
147
+
148
+ const booleanFilterConfig: FilterConfig<FilterFieldName, MockFilterState> = {
149
+ booleanField: {
150
+ filterKey: 'booleanField',
151
+ valueType: 'boolean',
152
+ kind: 'scalar',
153
+ },
154
+ }
155
+
156
+ const dateFilterConfig: FilterConfig<FilterFieldName, MockFilterState> = {
157
+ dateField: {
158
+ filterKey: 'dateField',
159
+ valueType: 'date',
160
+ kind: 'scalar',
161
+ },
162
+ }
163
+
164
+ const hierarchyFilterConfig: FilterConfig<FilterFieldName, MockFilterState> = {
165
+ hierarchyField: {
166
+ filterKey: 'hierarchyField',
167
+ valueType: 'hierarchy',
168
+ kind: 'hierarchy',
169
+ },
170
+ }
171
+
172
+ // Mock getFilterOptions function
173
+ const createMockGetFilterOptions =
174
+ (options: FilterOption[]) => (_args: { field: FilterFieldName; filters: MockFilterState }) => ({
175
+ queryKey: ['mockFilterOptions'],
176
+ queryFn: async () => options,
177
+ })
178
+
179
+ // Wrapper component for stories
180
+ const FilterWrapper = ({
181
+ field,
182
+ config,
183
+ options,
184
+ initialFilters = {},
185
+ }: {
186
+ field: FilterFieldName
187
+ config: FilterConfig<FilterFieldName, MockFilterState>
188
+ options: FilterOption[]
189
+ initialFilters?: MockFilterState
190
+ }) => {
191
+ const [filters, setFilters] = useState<MockFilterState>(initialFilters)
192
+ const [open, setOpen] = useState(false)
193
+
194
+ const handleChange = (val: FilterValue) => {
195
+ const fieldConfig = config[field]
196
+ if (fieldConfig) {
197
+ setFilters((prev) => ({
198
+ ...prev,
199
+ [fieldConfig.filterKey]: val,
200
+ }))
201
+ }
202
+ }
203
+
204
+ return (
205
+ <div className="flex flex-col gap-4 items-center">
206
+ <Popover open={open} onOpenChange={setOpen}>
207
+ <PopoverTrigger asChild>
208
+ <Button variant="outline">{open ? 'Filter Open' : 'Open Filter'}</Button>
209
+ </PopoverTrigger>
210
+ <PopoverContent className="w-[280px] p-0">
211
+ <TableFilter
212
+ open={open}
213
+ field={field}
214
+ filters={filters}
215
+ onChange={handleChange}
216
+ config={config}
217
+ getFilterOptions={createMockGetFilterOptions(options)}
218
+ />
219
+ </PopoverContent>
220
+ </Popover>
221
+
222
+ <div className="text-xs bg-muted p-2 rounded">
223
+ <pre>{JSON.stringify(filters, null, 2)}</pre>
224
+ </div>
225
+ </div>
226
+ )
227
+ }
228
+
229
+ export const StringFilter: Story = {
230
+ args: {} as any,
231
+ render: () => <FilterWrapper field="stringField" config={stringFilterConfig} options={stringOptions} />,
232
+ }
233
+
234
+ export const NumberFilter: Story = {
235
+ args: {} as any,
236
+ render: () => <FilterWrapper field="numberField" config={numberFilterConfig} options={numberOptions} />,
237
+ }
238
+
239
+ export const BooleanFilter: Story = {
240
+ args: {} as any,
241
+ render: () => <FilterWrapper field="booleanField" config={booleanFilterConfig} options={booleanOptions} />,
242
+ }
243
+
244
+ export const DateFilter: Story = {
245
+ args: {} as any,
246
+ render: () => <FilterWrapper field="dateField" config={dateFilterConfig} options={dateOptions} />,
247
+ }
248
+
249
+ export const HierarchyFilter: Story = {
250
+ args: {} as any,
251
+ render: () => <FilterWrapper field="hierarchyField" config={hierarchyFilterConfig} options={hierarchyOptions} />,
252
+ }
253
+
254
+ export const WithOutMatchesOnCrossFiltering: Story = {
255
+ args: {} as any,
256
+ render: () => (
257
+ <FilterWrapper
258
+ field="stringField"
259
+ config={stringFilterConfig}
260
+ options={stringOptions
261
+ .map((opt) => (opt.value === 'banana' || opt.value === 'elderberry' ? { ...opt, hasMatches: false } : opt))
262
+ .sort((a, b) => (a.hasMatches && !b.hasMatches ? -1 : !a.hasMatches && b.hasMatches ? 1 : 0))} // Simulate "banana" and "elderberry" having no matches due to cross-filtering
263
+ />
264
+ ),
265
+ }