Skip to content

Commit 5417b8c

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
2 parents 8d62501 + 8b8c15c commit 5417b8c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2317
-916
lines changed

adminforth/dataConnectors/baseConnector.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,13 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
253253
resource: AdminForthResource; record: any; adminUser: any;
254254
}): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
255255
// transform value using setFieldValue and call createRecordOriginalValues
256-
256+
257257
const filledRecord = {...record};
258258
const recordWithOriginalValues = {...record};
259259

260260
for (const col of resource.dataSourceColumns) {
261261
if (col.fillOnCreate) {
262-
if (filledRecord[col.name] === undefined) {
262+
if (filledRecord[col.name] === undefined || (Array.isArray(filledRecord[col.name]) && filledRecord[col.name].length === 0)) {
263263
filledRecord[col.name] = col.fillOnCreate({
264264
initialRecord: record,
265265
adminUser

adminforth/documentation/docs/tutorial/03-Customization/01-branding.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ const admin = new AdminForth({
141141
enabled: true, // Optional: Enable the collapsible icon-only sidebar feature
142142
//diff-add
143143
logo: '@@/logo.svg', // Optional: Custom logo to display in icon-only mode
144+
//diff-add
145+
expandedSidebarWidth: '18.5rem', // Optional: sets the expanded sidebar width, defaults to 16.5rem
144146
//diff-add
145147
},
146148
},

adminforth/documentation/docs/tutorial/03-Customization/07-alert.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ Next variants are supported:
2424
* `warning`
2525
* `info`
2626

27-
// ...existing code...
2827
### Making alert responsive
2928
You can pass buttons in the alert method and receive a response like:
3029

adminforth/documentation/docs/tutorial/03-Customization/09-Actions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ import { admin } from '../index';
204204
//diff-add
205205
await stmt.run(...selectedIds);
206206
//diff-add
207-
return { ok: true, error: false, successMessage: `Marked ${selectedIds.length} apartments as listed` };
207+
return { ok: true, successMessage: `Marked ${selectedIds.length} apartments as listed` };
208208
//diff-add
209209
},
210210
//diff-add

adminforth/documentation/docs/tutorial/03-Customization/10-menuConfiguration.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ E.g. create group "Blog" with Items who link to resource "posts" and "categories
2323
```ts title='./index.ts'
2424
{
2525
...
26-
menu: {
26+
menu: [
2727
{
2828
label: 'Blog',
2929
icon: 'flowbite:brain-solid',
@@ -46,7 +46,7 @@ E.g. create group "Blog" with Items who link to resource "posts" and "categories
4646
icon: 'flowbite:folder-duplicate-outline',
4747
resourceId: 'adminuser',
4848
},
49-
},
49+
],
5050
...
5151
}
5252
```

adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default {
6363
}
6464
```
6565

66-
You can also specify on which page you want to create or delete groups. If you assign null, the groups will disappear from this page.
66+
You can also specify on which page you want to create groups.
6767

6868
```typescript title="./resources/apartments.ts"
6969
export default {
@@ -89,10 +89,6 @@ export default {
8989
}
9090
//diff-add
9191
],
92-
//diff-add
93-
editFieldGroups: null,
94-
//diff-add
95-
showFieldGroups: null,
9692
}
9793
}
9894
```

adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,141 @@ async function loadPageData(data) {
973973
```
974974
> 👆 The page size is used as the limit for pagination.
975975
976+
### Sorting
977+
978+
Table supports column sorting out of the box.
979+
980+
- By default, columns are NOT sortable. Enable sorting per column with `sortable: true`.
981+
- Clicking a sortable header cycles sorting in a tri-state order:
982+
- none → ascending → descending → none
983+
- When it returns to "none", the sorting is cleared.
984+
985+
Basic example (client-side sorting when `data` is an array):
986+
```html
987+
<Table
988+
:columns="[
989+
{ label: 'Name', fieldName: 'name', sortable: true },
990+
{ label: 'Age', fieldName: 'age', sortable: true },
991+
// disable sort for a specific column
992+
{ label: 'Country', fieldName: 'country', sortable: false },
993+
]"
994+
:data="[
995+
{ name: 'John', age: 30, country: 'US' },
996+
{ name: 'Rick', age: 25, country: 'CA' },
997+
{ name: 'Alice', age: 35, country: 'BR' },
998+
{ name: 'Colin', age: 40, country: 'AU' },
999+
]"
1000+
:pageSize="3"
1001+
/>
1002+
```
1003+
1004+
You can also predefine a default sort:
1005+
1006+
```html
1007+
<Table
1008+
:columns="[
1009+
{ label: 'Name', fieldName: 'name', sortable: true },
1010+
{ label: 'Age', fieldName: 'age', sortable: true },
1011+
{ label: 'Country', fieldName: 'country' },
1012+
]"
1013+
:data="rows"
1014+
defaultSortField="age"
1015+
defaultSortDirection="desc"
1016+
/>
1017+
```
1018+
1019+
Notes:
1020+
- Client-side sorting supports nested field paths using dot-notation, e.g. `user.name`.
1021+
- When a column is not currently sorted, a subtle double-arrow icon is shown; arrows switch up/down for ascending/descending.
1022+
1023+
#### Nested field path sorting
1024+
1025+
You can sort by nested properties of objects in rows using dot-notation in `fieldName`.
1026+
1027+
Example:
1028+
1029+
```html
1030+
<Table
1031+
:columns="[
1032+
{ label: 'User Name', fieldName: 'user.name', sortable: true },
1033+
{ label: 'User Email', fieldName: 'user.email', sortable: true },
1034+
{ label: 'City', fieldName: 'user.address.city', sortable: true },
1035+
{ label: 'Country', fieldName: 'user.address.country' },
1036+
]"
1037+
:data="[
1038+
{ user: { name: 'Alice', email: '[email protected]', address: { city: 'Berlin', country: 'DE' } } },
1039+
{ user: { name: 'Bob', email: '[email protected]', address: { city: 'Paris', country: 'FR' } } },
1040+
{ user: { name: 'Carlos', email: '[email protected]', address: { city: 'Madrid', country: 'ES' } } },
1041+
{ user: { name: 'Dana', email: '[email protected]', address: { city: 'Rome', country: 'IT' } } },
1042+
{ user: { name: 'Eve', email: '[email protected]', address: { city: null, country: 'US' } } },
1043+
]"
1044+
:pageSize="3"
1045+
/>
1046+
```
1047+
1048+
Behavior details:
1049+
- The path is split on dots and resolved step by step: `user.address.city`.
1050+
- Missing or `null` nested values are pushed to the bottom for ascending order (and top for descending) because `null/undefined` are treated as greater than defined values in asc ordering.
1051+
- Works the same for client-side sorting and for server-side loaders: when using an async loader the same `sortField` (e.g. `user.address.city`) is passed so you can implement equivalent ordering on the backend.
1052+
- Date objects at nested paths are detected and compared chronologically.
1053+
- Numeric comparison is stable for mixed numeric strings via Intl.Collator with numeric option.
1054+
1055+
Edge cases to consider in your own data:
1056+
- Deeply missing branches like `user.profile.settings.locale` simply result in `undefined` and will follow the null ordering logic above.
1057+
- Arrays are not traversed; if you need array-specific sorting you should pre-normalize data into scalar fields before passing to the table.
1058+
1059+
### Server-side sorting
1060+
1061+
When you provide an async function to `data`, the table will pass the current sort along with pagination params.
1062+
1063+
Signature of the loader receives:
1064+
1065+
```ts
1066+
type LoaderArgs = {
1067+
offset: number;
1068+
limit: number;
1069+
sortField?: string; // undefined when unsorted
1070+
sortDirection?: 'asc' | 'desc'; // only when sortField is set
1071+
}
1072+
```
1073+
1074+
Example using `fetch`:
1075+
1076+
```ts
1077+
async function loadPageData({ offset, limit, sortField, sortDirection }) {
1078+
const url = new URL('/api/products', window.location.origin);
1079+
url.searchParams.set('offset', String(offset));
1080+
url.searchParams.set('limit', String(limit));
1081+
if (sortField) url.searchParams.set('sortField', sortField);
1082+
if (sortField && sortDirection) url.searchParams.set('sortDirection', sortDirection);
1083+
1084+
const { data, total } = callAdminForthApi('getProducts', {limit, offset, sortField, sortDirection});
1085+
return { data, total };
1086+
}
1087+
1088+
<Table
1089+
:columns="[
1090+
{ label: 'ID', fieldName: 'id' },
1091+
{ label: 'Title', fieldName: 'title' },
1092+
{ label: 'Price', fieldName: 'price' },
1093+
]"
1094+
:data="loadPageData"
1095+
:pageSize="10"
1096+
/>
1097+
```
1098+
1099+
Events you can listen to:
1100+
1101+
```html
1102+
<Table
1103+
:columns="columns"
1104+
:data="loadPageData"
1105+
@update:sortField="(f) => currentSortField = f"
1106+
@update:sortDirection="(d) => currentSortDirection = d"
1107+
@sort-change="({ field, direction }) => console.log('sort changed', field, direction)"
1108+
/>
1109+
```
1110+
9761111
### Table loading states
9771112

9781113
For tables where you load data externally and pass them to `data` prop as array (including case with front-end pagination) you might want to show skeleton loaders in table externaly using `isLoading` props.

adminforth/documentation/docs/tutorial/07-Plugins/03-ForeignInlineList.md

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,96 @@ Add to your `'adminuser'` resource configuration the plugin instance:
7272
}
7373
```
7474

75-
You can use `modifyTableResourceConfig` callback to modify what columns to show in the list and filter of the foreign table.
75+
You can use the `modifyTableResourceConfig` callback to modify which columns to show in the list and filter of the foreign table.
7676

7777
![alt text](ForeignInlineList.png)
7878

7979
> 👆 To make plugin work, the specified resource (defined with `foreignResourceId`) should have one (and only one) column that refers to the current resource on which you add a plugin.
80-
> In our case we add plugin to `adminuser` resource, so the `aparts` resource should have one column with `foreignResource.resourceId` equal to `adminuser` resourceId.
80+
> In our case we add plugin to `adminuser` resource, so the `aparts` resource should have one column with `foreignResource.resourceId` equal to `adminuser` resourceId.
81+
82+
## Default filters
83+
84+
If you need to add default filters for the foreign resource based on your current record (for example show apartment only from Italy, when user have country Italy), you can use defaultFilters callback:
85+
>👆 This example won't work until you'll add counrty field in your adminuser resource and it's only for demonstrating concept of callback
86+
87+
```ts title="./resources/adminuser.ts"
88+
89+
...
90+
91+
new ForeignInlineListPlugin({
92+
93+
...
94+
//diff-add
95+
defaultFilters: (record: any) => {
96+
//diff-add
97+
return [
98+
//diff-add
99+
{
100+
//diff-add
101+
field: "country",
102+
//diff-add
103+
operator: AdminForthFilterOperators.EQ,
104+
//diff-add
105+
value: record.country,
106+
//diff-add
107+
}
108+
//diff-add
109+
]
110+
//diff-add
111+
}
112+
113+
...
114+
115+
})
116+
117+
...
118+
119+
```
120+
121+
>👆It also makes sense to modify the table resource and hide the country field from filters, because this value is hardcoded and equals the country from the record:
122+
123+
124+
```ts
125+
126+
...
127+
128+
new ForeignInlineListPlugin({
129+
130+
...
131+
132+
//diff-add
133+
modifyTableResourceConfig: (resourceConfig: AdminForthResource) => {
134+
//diff-add
135+
const column = resourceConfig.columns.find((c: AdminForthResourceColumn) => c.name === 'country')!.showIn = {
136+
//diff-add
137+
list: true,
138+
//diff-add
139+
show: true,
140+
//diff-add
141+
edit: true,
142+
//diff-add
143+
create: true,
144+
//diff-add
145+
filter: false
146+
//diff-add
147+
};
148+
//diff-add
149+
},
150+
151+
defaultFilters: (record: any) => {
152+
return [
153+
{
154+
field: "country",
155+
operator: AdminForthFilterOperators.EQ,
156+
value: record.country,
157+
}
158+
]
159+
}
160+
161+
...
162+
163+
})
164+
165+
...
166+
167+
```

adminforth/documentation/docs/tutorial/07-Plugins/05-upload.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ model apartments {
9999
Migrate prisma schema:
100100
101101
```bash
102-
npx prisma migrate dev --name add-apartment-image
102+
npm run makemigration -- --name add-apartment-image ; npm run migrate:local
103103
```
104104
105105
Add column to `aparts` resource configuration:

adminforth/documentation/docs/tutorial/07-Plugins/11-oauth.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ model adminuser {
8484
2. Run the migration:
8585

8686
```bash
87-
npx prisma migrate dev --name add-email-confirmed-to-adminuser
87+
npm run makemigration -- --name add-email-confirmed-to-adminuser ; npm run migrate:local
8888
```
8989

9090
3. Configure the plugin with `emailConfirmedField`:

0 commit comments

Comments
 (0)