Skip to content
Merged

Next #418

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
a80bc27
feat: implement sortable columns with visual indicators and sorting l…
NoOne7135 Nov 4, 2025
91ef36b
fix: update sortDirection emission to handle undefined values correctly
NoOne7135 Nov 4, 2025
b963cde
fix: enhance sorting indicators and logic for table columns
NoOne7135 Nov 4, 2025
3a66c2c
feat: update table component to support configurable sortable columns…
NoOne7135 Nov 4, 2025
05bf682
chore: update dependencies and devDependencies in package.json
NoOne7135 Nov 5, 2025
bdefd99
fix: correct import path for vue3-json-viewer CSS
NoOne7135 Nov 5, 2025
fd2f927
fix: update allowed injection keys for the 'list' page in ConfigValid…
NoOne7135 Nov 7, 2025
a8f54b6
feat: enhance table sorting functionality with per-column control and…
NoOne7135 Nov 10, 2025
df06a7b
fix: correct tri-state sorting description in documentation and updat…
NoOne7135 Nov 10, 2025
f11fd8c
Merge pull request #414 from devforth/feature/AdminForth/976/add-the-…
NoOne7135 Nov 10, 2025
92c3685
fix: reset current page to 1 on sort change and improve sort-change e…
NoOne7135 Nov 10, 2025
aefb058
fix: reset current page to 1 on sort change to ensure correct data di…
NoOne7135 Nov 10, 2025
ce573d8
k3s-deployment blogpost
kirilldorr Nov 4, 2025
00e883e
Updated blog content on k3s
kirilldorr Nov 13, 2025
8f16bae
Updated blog content on k3s
kirilldorr Nov 13, 2025
4066efb
add build time comparison table
kirilldorr Nov 13, 2025
d188a5f
rework build time comparison table
kirilldorr Nov 13, 2025
031cde2
rework build time comparison table
kirilldorr Nov 13, 2025
667c5b9
fix: update version of adminforth on live demo
yaroslav8765 Nov 14, 2025
d45c091
fix: reneme bulk-ai action from "Process" to "Process with AI" on liv…
yaroslav8765 Nov 14, 2025
520bdbc
feat: add ability in dropzone to remove selected files and upload the…
SerVitasik Nov 18, 2025
b39df80
fix: show fast record actions in the ShowView
yaroslav8765 Nov 18, 2025
ae10643
fix: remove debug log for options change in AreaChart component
NoOne7135 Nov 19, 2025
7f891d7
Merge branch 'next' of https://github.com/devforth/adminforth into next
NoOne7135 Nov 19, 2025
0916a21
fix: correct input reset method name and enhance extension handling i…
SerVitasik Nov 19, 2025
1c374a0
fix: add duplicate file check in Dropzone component to prevent re-sel…
SerVitasik Nov 19, 2025
ac49402
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 19, 2025
8a89ff4
docs: update docs for the bulk-ai-flow plugin
yaroslav8765 Nov 20, 2025
2787f3a
fix: fix create-app command to support prisma 7 version
yaroslav8765 Nov 21, 2025
a5a24ad
feat: rowClick emit to the afcl table
yaroslav8765 Nov 21, 2025
e65aa5d
fix: reset selected and stored files in Dropzone component when no fi…
SerVitasik Nov 21, 2025
eae865c
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 21, 2025
5c67a4c
fix: update MenuLink component to enhance badge alignment with flexbox
SerVitasik Nov 21, 2025
9b3a22d
fix: pass id of created record to the afterCreate hook and add "recor…
yaroslav8765 Nov 21, 2025
86f2141
Merge pull request #416 from devforth/feature/AdminForth/1006/add-vir…
yaroslav8765 Nov 21, 2025
4f06207
fix: fix recordWithVirtualColumns in afterCreate hook
yaroslav8765 Nov 21, 2025
33e0390
Merge pull request #417 from devforth/feature/AdminForth/1006/add-vir…
yaroslav8765 Nov 21, 2025
b1cc3a1
docs: update prisma migration examples in setup guide for the upload,…
yaroslav8765 Nov 23, 2025
39d5187
fix: adjust font size for filter options in Filters component
yaroslav8765 Nov 24, 2025
fcdbac2
fix: convert margin left from px to rem for sidebar
SerVitasik Nov 24, 2025
5675078
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 24, 2025
26c8871
fix: add class to main content wrapper for improved layout
SerVitasik Nov 24, 2025
41df0e3
fix: update Sidebar component styles for improved layout and spacing
SerVitasik Nov 24, 2025
f57cbc9
docs: add docs for the many2many plugin
yaroslav8765 Nov 24, 2025
3f714c3
fix: fix users menu items render bug
yaroslav8765 Nov 24, 2025
4b848f9
feat: add customizable expanded sidebar width option
SerVitasik Nov 24, 2025
f7bd633
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 24, 2025
b7ed7c1
fix: update adminforth version on live demo
yaroslav8765 Nov 24, 2025
8228f12
Merge branch 'next' of github.com:devforth/adminforth into next
yaroslav8765 Nov 24, 2025
2322fdf
fix: typo in in StorageAdamter method by adding one with the correct …
yaroslav8765 Nov 24, 2025
4000831
chore: remove debug console.logs from dev demo
yaroslav8765 Nov 25, 2025
d5d7bc9
fix: fix fillOnCreate for the isArray columns
yaroslav8765 Nov 25, 2025
33dcc33
fix: add debug logs for the userMenu items
yaroslav8765 Nov 25, 2025
03d3e0c
fix: fix jumping cursor in pagination inputs
yaroslav8765 Nov 25, 2025
82ba4d4
feat: enhance file name display in Dropzone component with truncation…
SerVitasik Nov 25, 2025
10264b1
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 25, 2025
61af759
feat: allow to hide first tab in vertical tabs if there is only one tab
yaroslav8765 Nov 25, 2025
18c5e2a
fix: hide tabs in setting view when there is only one page
yaroslav8765 Nov 25, 2025
cc6c8ed
Merge branch 'next' of github.com:devforth/adminforth into next
yaroslav8765 Nov 25, 2025
ea75e06
feat: add isVisible callback for the settings pages
yaroslav8765 Nov 25, 2025
305bbc2
fix: hide setting button when there is no settings user allowed to see
yaroslav8765 Nov 25, 2025
6377ae7
docs: fix docs example for the actions
yaroslav8765 Nov 26, 2025
82a83a2
docs: fix docs for the menu config page
yaroslav8765 Nov 26, 2025
7a2b434
feat: add scroll to invalid fields and error notification when user t…
yaroslav8765 Nov 26, 2025
663acba
fix: add extra check if record with provided PK already exists (for c…
yaroslav8765 Nov 26, 2025
3f7bc60
fix: add debug logs for the userMenu items
yaroslav8765 Nov 26, 2025
8e76e8d
docs: fix docs for the standart pages tuning example for the fields …
yaroslav8765 Nov 26, 2025
e879f8c
feat: add headerOnly layout value to sidebarAndHeader option
SerVitasik Nov 26, 2025
384fa20
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 26, 2025
7782b18
fix: enhance error handling for showIf conditions in resource validation
dominicDeCoco-bravo Nov 26, 2025
29fc947
fix: fix invalid hover color for the sticky columns
yaroslav8765 Nov 27, 2025
2991287
fix: remove unnecessary headerOnlyLayout condition from navigation vi…
SerVitasik Nov 27, 2025
0cace2f
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Nov 27, 2025
c55546b
fix: remove margin left for header only mode
SerVitasik Nov 27, 2025
1e81e91
fix: move "FilterParams" data type to the Common.ts
yaroslav8765 Nov 27, 2025
03b926f
docs: update docs for the foreign inline list
yaroslav8765 Nov 27, 2025
371ca0e
feat: add scroll to invalid fields and error notification when user t…
yaroslav8765 Nov 27, 2025
dd4708c
fix: update adminforth version on live demo
yaroslav8765 Nov 26, 2025
8212d06
Merge branch 'main' into next
yaroslav8765 Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions adminforth/dataConnectors/baseConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
resource: AdminForthResource; record: any; adminUser: any;
}): Promise<{ error?: string; ok: boolean; createdRecord?: any; }> {
// transform value using setFieldValue and call createRecordOriginalValues

const filledRecord = {...record};
const recordWithOriginalValues = {...record};

for (const col of resource.dataSourceColumns) {
if (col.fillOnCreate) {
if (filledRecord[col.name] === undefined) {
if (filledRecord[col.name] === undefined || (Array.isArray(filledRecord[col.name]) && filledRecord[col.name].length === 0)) {
filledRecord[col.name] = col.fillOnCreate({
initialRecord: record,
adminUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const admin = new AdminForth({
enabled: true, // Optional: Enable the collapsible icon-only sidebar feature
//diff-add
logo: '@@/logo.svg', // Optional: Custom logo to display in icon-only mode
//diff-add
expandedSidebarWidth: '18.5rem', // Optional: sets the expanded sidebar width, defaults to 16.5rem
//diff-add
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ Next variants are supported:
* `warning`
* `info`

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ import { admin } from '../index';
//diff-add
await stmt.run(...selectedIds);
//diff-add
return { ok: true, error: false, successMessage: `Marked ${selectedIds.length} apartments as listed` };
return { ok: true, successMessage: `Marked ${selectedIds.length} apartments as listed` };
//diff-add
},
//diff-add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ E.g. create group "Blog" with Items who link to resource "posts" and "categories
```ts title='./index.ts'
{
...
menu: {
menu: [
{
label: 'Blog',
icon: 'flowbite:brain-solid',
Expand All @@ -46,7 +46,7 @@ E.g. create group "Blog" with Items who link to resource "posts" and "categories
icon: 'flowbite:folder-duplicate-outline',
resourceId: 'adminuser',
},
},
],
...
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default {
}
```

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.
You can also specify on which page you want to create groups.

```typescript title="./resources/apartments.ts"
export default {
Expand All @@ -89,10 +89,6 @@ export default {
}
//diff-add
],
//diff-add
editFieldGroups: null,
//diff-add
showFieldGroups: null,
}
}
```
Expand Down
135 changes: 135 additions & 0 deletions adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,141 @@ async function loadPageData(data) {
```
> 👆 The page size is used as the limit for pagination.

### Sorting

Table supports column sorting out of the box.

- By default, columns are NOT sortable. Enable sorting per column with `sortable: true`.
- Clicking a sortable header cycles sorting in a tri-state order:
- none → ascending → descending → none
- When it returns to "none", the sorting is cleared.

Basic example (client-side sorting when `data` is an array):
```html
<Table
:columns="[
{ label: 'Name', fieldName: 'name', sortable: true },
{ label: 'Age', fieldName: 'age', sortable: true },
// disable sort for a specific column
{ label: 'Country', fieldName: 'country', sortable: false },
]"
:data="[
{ name: 'John', age: 30, country: 'US' },
{ name: 'Rick', age: 25, country: 'CA' },
{ name: 'Alice', age: 35, country: 'BR' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
:pageSize="3"
/>
```

You can also predefine a default sort:

```html
<Table
:columns="[
{ label: 'Name', fieldName: 'name', sortable: true },
{ label: 'Age', fieldName: 'age', sortable: true },
{ label: 'Country', fieldName: 'country' },
]"
:data="rows"
defaultSortField="age"
defaultSortDirection="desc"
/>
```

Notes:
- Client-side sorting supports nested field paths using dot-notation, e.g. `user.name`.
- When a column is not currently sorted, a subtle double-arrow icon is shown; arrows switch up/down for ascending/descending.

#### Nested field path sorting

You can sort by nested properties of objects in rows using dot-notation in `fieldName`.

Example:

```html
<Table
:columns="[
{ label: 'User Name', fieldName: 'user.name', sortable: true },
{ label: 'User Email', fieldName: 'user.email', sortable: true },
{ label: 'City', fieldName: 'user.address.city', sortable: true },
{ label: 'Country', fieldName: 'user.address.country' },
]"
:data="[
{ user: { name: 'Alice', email: 'alice@example.com', address: { city: 'Berlin', country: 'DE' } } },
{ user: { name: 'Bob', email: 'bob@example.com', address: { city: 'Paris', country: 'FR' } } },
{ user: { name: 'Carlos', email: 'carlos@example.com', address: { city: 'Madrid', country: 'ES' } } },
{ user: { name: 'Dana', email: 'dana@example.com', address: { city: 'Rome', country: 'IT' } } },
{ user: { name: 'Eve', email: 'eve@example.com', address: { city: null, country: 'US' } } },
]"
:pageSize="3"
/>
```

Behavior details:
- The path is split on dots and resolved step by step: `user.address.city`.
- 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.
- 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.
- Date objects at nested paths are detected and compared chronologically.
- Numeric comparison is stable for mixed numeric strings via Intl.Collator with numeric option.

Edge cases to consider in your own data:
- Deeply missing branches like `user.profile.settings.locale` simply result in `undefined` and will follow the null ordering logic above.
- Arrays are not traversed; if you need array-specific sorting you should pre-normalize data into scalar fields before passing to the table.

### Server-side sorting

When you provide an async function to `data`, the table will pass the current sort along with pagination params.

Signature of the loader receives:

```ts
type LoaderArgs = {
offset: number;
limit: number;
sortField?: string; // undefined when unsorted
sortDirection?: 'asc' | 'desc'; // only when sortField is set
}
```

Example using `fetch`:

```ts
async function loadPageData({ offset, limit, sortField, sortDirection }) {
const url = new URL('/api/products', window.location.origin);
url.searchParams.set('offset', String(offset));
url.searchParams.set('limit', String(limit));
if (sortField) url.searchParams.set('sortField', sortField);
if (sortField && sortDirection) url.searchParams.set('sortDirection', sortDirection);

const { data, total } = callAdminForthApi('getProducts', {limit, offset, sortField, sortDirection});
return { data, total };
}

<Table
:columns="[
{ label: 'ID', fieldName: 'id' },
{ label: 'Title', fieldName: 'title' },
{ label: 'Price', fieldName: 'price' },
]"
:data="loadPageData"
:pageSize="10"
/>
```

Events you can listen to:

```html
<Table
:columns="columns"
:data="loadPageData"
@update:sortField="(f) => currentSortField = f"
@update:sortDirection="(d) => currentSortDirection = d"
@sort-change="({ field, direction }) => console.log('sort changed', field, direction)"
/>
```

### Table loading states

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,96 @@ Add to your `'adminuser'` resource configuration the plugin instance:
}
```

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

![alt text](ForeignInlineList.png)

> 👆 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.
> In our case we add plugin to `adminuser` resource, so the `aparts` resource should have one column with `foreignResource.resourceId` equal to `adminuser` resourceId.
> In our case we add plugin to `adminuser` resource, so the `aparts` resource should have one column with `foreignResource.resourceId` equal to `adminuser` resourceId.

## Default filters

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:
>👆 This example won't work until you'll add counrty field in your adminuser resource and it's only for demonstrating concept of callback

```ts title="./resources/adminuser.ts"

...

new ForeignInlineListPlugin({

...
//diff-add
defaultFilters: (record: any) => {
//diff-add
return [
//diff-add
{
//diff-add
field: "country",
//diff-add
operator: AdminForthFilterOperators.EQ,
//diff-add
value: record.country,
//diff-add
}
//diff-add
]
//diff-add
}

...

})

...

```

>👆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:


```ts

...

new ForeignInlineListPlugin({

...

//diff-add
modifyTableResourceConfig: (resourceConfig: AdminForthResource) => {
//diff-add
const column = resourceConfig.columns.find((c: AdminForthResourceColumn) => c.name === 'country')!.showIn = {
//diff-add
list: true,
//diff-add
show: true,
//diff-add
edit: true,
//diff-add
create: true,
//diff-add
filter: false
//diff-add
};
//diff-add
},

defaultFilters: (record: any) => {
return [
{
field: "country",
operator: AdminForthFilterOperators.EQ,
value: record.country,
}
]
}

...

})

...

```
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ model apartments {
Migrate prisma schema:

```bash
npx prisma migrate dev --name add-apartment-image
npm run makemigration -- --name add-apartment-image ; npm run migrate:local
```

Add column to `aparts` resource configuration:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ model adminuser {
2. Run the migration:

```bash
npx prisma migrate dev --name add-email-confirmed-to-adminuser
npm run makemigration -- --name add-email-confirmed-to-adminuser ; npm run migrate:local
```

3. Configure the plugin with `emailConfirmedField`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ model adminuser {
Run the migration:

```bash
npx prisma migrate dev --name add-email-confirmed
npm run makemigration -- --name add-email-confirmed ; npm run migrate:local
```

Then update your resource configuration:
Expand Down
Loading