@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.
- package/dist/backend-view/model-view-service.generator.js +4 -1
- package/dist/backend-view/model-view-service.generator.js.map +1 -1
- package/dist/backend-view/template/query.utils.test.ts +34 -0
- package/dist/backend-view/template/query.utils.ts +89 -0
- package/dist/frontend-admin/admin.generator.d.ts +9 -0
- package/dist/frontend-admin/admin.generator.js +19 -0
- package/dist/frontend-admin/admin.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-sidebar.generator.js +1 -1
- package/dist/frontend-admin/generators/audit-log-sidebar.generator.js +107 -113
- package/dist/frontend-admin/generators/audit-log-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/comment-sidebar.generator.d.ts +9 -0
- package/dist/frontend-admin/generators/comment-sidebar.generator.js +246 -0
- package/dist/frontend-admin/generators/comment-sidebar.generator.js.map +1 -0
- package/dist/frontend-admin/generators/detail-sidebar.generator.d.ts +9 -0
- package/dist/frontend-admin/generators/detail-sidebar.generator.js +148 -0
- package/dist/frontend-admin/generators/detail-sidebar.generator.js.map +1 -0
- package/dist/frontend-admin/generators/model-admin-page.generator.js +40 -6
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-core/frontend.generator.js +2 -1
- package/dist/frontend-core/frontend.generator.js.map +1 -1
- package/dist/frontend-core/template/docs/LOGIN_PROCESS.d2 +372 -0
- package/dist/frontend-core/template/docs/LOGIN_PROCESS.md +214 -0
- package/dist/frontend-core/template/docs/LOGIN_PROCESS.svg +914 -0
- package/dist/frontend-core/template/src/components/admin/table-filter.stories.tsx +265 -0
- package/dist/frontend-core/template/src/components/admin/table-filter.tsx +224 -128
- package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +159 -0
- package/dist/frontend-core/template/src/context-providers/auth-context-provider.tsx +54 -43
- package/dist/frontend-core/template/src/hooks/use-table-view-config.tsx +249 -0
- package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +5 -5
- package/dist/frontend-core/template/src/pages/error/auth-error.page.tsx +37 -0
- package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +24 -40
- package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +8 -10
- package/dist/frontend-core/template/src/pages/login/login.page.tsx +39 -13
- package/dist/frontend-core/template/src/pages/unauthorized/unauthorized.page.tsx +2 -2
- package/dist/frontend-core/template/src/routes/_auth-routes.tsx +21 -8
- package/dist/frontend-core/template/src/routes/auth-error.tsx +19 -0
- package/dist/frontend-core/template/vite.config.ts +5 -0
- package/dist/frontend-core/types/hook.d.ts +1 -1
- package/dist/frontend-tables/generators/model-table.generator.js +30 -5
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/types/template/query.types.ts +19 -1
- 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
|
+
}
|