Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5346d39
feat : add dashboard constants actions catalog and delete helper
mohammadsherif0 Jul 26, 2026
2da9bce
refactor : use BasicButton in coordinator and confirm modal
mohammadsherif0 Jul 26, 2026
9d8f496
feat : add DashboardListPage component
mohammadsherif0 Jul 26, 2026
9f5008b
refactor : use ListPage for card-and-table dashboards
mohammadsherif0 Jul 26, 2026
9382a05
refactor : align study session and submission dashboard row actions
mohammadsherif0 Jul 26, 2026
b674ba1
Merge branch 'dev' into feat-272-dashboard_refactor
mohammadsherif0 Aug 16, 2026
a954996
refactor: use shared dashboard actions and soft-delete helper
mohammadsherif0 Aug 16, 2026
0a53059
docs: document dashboard list-page foundation
mohammadsherif0 Aug 16, 2026
4d92a3a
fix: reset default project only after confirmed delete
mohammadsherif0 Aug 16, 2026
811591b
docs: name jsdoc params on dashboard helpers
mohammadsherif0 Aug 16, 2026
cec0238
Merge branch 'dev' into feat-272-dashboard_refactor
mohammadsherif0 Sep 5, 2026
bc7034d
fix: keep dashboard table defaults when withSearch gets extra options
mohammadsherif0 Sep 5, 2026
749d730
fix: resolve confirmSoftDelete error toasts through i18n
mohammadsherif0 Sep 5, 2026
f4d9ccb
docs: use i18n in dashboard list-page and modal examples
mohammadsherif0 Sep 5, 2026
d2ff9f6
fix: wrap confirm and coordinator footer buttons in btn-group
mohammadsherif0 Sep 5, 2026
6e01ad0
fix: use assignment name in submissions modal title
mohammadsherif0 Sep 7, 2026
cb6e6cb
refactor: convert socket profiler to dashboard list page
mohammadsherif0 Sep 7, 2026
67d5db7
refactor: use catalog open and edit icons on templates and workflows
mohammadsherif0 Sep 7, 2026
052dda4
style: use secondary for dashboard header utility buttons
mohammadsherif0 Sep 7, 2026
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
75 changes: 43 additions & 32 deletions docs/source/for_developers/examples/table.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,55 +142,66 @@ To apply the migration, we have to run the command ``make init``.
-----------------------

The last step is to create a new vue dashboard component in the folder ``./frontend/src/components/dashboard`` with the same name we defined in the navigation entry ``ExampleTable.vue``.
We make use of several basic components, see :doc:`../frontend/base_components` for more details.

For a card + table list page, use ``DashboardListPage``. Do not build a raw ``Card`` + ``BasicTable``
shell. Table options and height come from the list page. Full recipe (row buttons, filters, soft
delete): :doc:`../frontend/components/dashboard`. The shell itself is documented in
:doc:`../frontend/basic/dashboard`.

.. code-block:: html

<template>
<Card title="ExampleTable">
<template #body>
<Table
:columns="columns"
:data="data"
:options="options"
/>
</template>
</Card>
<DashboardListPage
title="Example Table Data"
:columns="columns"
:data="data"
:buttons="buttons"
@action="action"
/>
</template>

.. code-block:: javascript

<script>
import Table from "@/basic/table/Table.vue";
import Card from "@/basic/Card.vue";
import DashboardListPage from "@/basic/dashboard/ListPage.vue";
import { dashboardRowAction } from "@/basic/dashboard/actions.js";

export default {
name: "Log",
components: {Card, Table},
name: "ExampleTable",
subscribeTable: ["example_table"],
components: { DashboardListPage },
data() {
return {
options: {
striped: true,
hover: true,
bordered: false,
borderless: false,
small: false,
pagination: 30,
},
columns: [
{name: "User", key: "userId", sortable: true},
{name: "Username", key: "creator_name", sortable: true},
{name: "CreatedAt", key: "createdAt", sortable: true},
{name: "Text", key: "exampleText"},
{ name: "User", key: "userId", sortable: true },
{ name: "Username", key: "creator_name", sortable: true },
{ name: "CreatedAt", key: "createdAt", sortable: true },
{ name: "Text", key: "exampleText" },
],
}
};
},
computed: {
data() {
return this.$store.getters["auto/example_table/getAll"];
}
}
}
return this.$store.getters["table/example_table/getAll"];
},
buttons() {
return [
dashboardRowAction("edit", {
title: "Edit",
action: "edit",
}),
];
},
},
methods: {
action(data) {
if (data.action === "edit") {
// open your edit modal with data.params
}
},
},
};
</script>

Of course, you can add more columns and more complex components to the table.
See also the already existing code in the repository.
See also ``Tags.vue`` and ``Projects.vue`` in the repository.
91 changes: 89 additions & 2 deletions docs/source/for_developers/frontend/basic/dashboard.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You can use it by simply importing it and inserting the headerElements, body and

.. code-block:: javascript

import BasicCard from '@/basic/Card.vue';
import BasicCard from '@/basic/dashboard/card/Card.vue';

export default {
name: 'CardExample',
Expand Down Expand Up @@ -51,6 +51,93 @@ You can use it by simply importing it and inserting the headerElements, body and
- Boolean


**DashboardListPage**

Shared shell for a dashboard list page: a card wrapping ``BasicTable``. Import from
``@/basic/dashboard/ListPage.vue``. How to wire columns, row buttons, filters, and soft delete
on a dashboard page: :doc:`../components/dashboard`.

.. code-block:: html

<DashboardListPage
title="Tag Sets"
:columns="columns"
:data="tagSets"
:buttons="buttons"
@action="action"
>
<template #headerActions>
<!-- header buttons -->
</template>
</DashboardListPage>

.. code-block:: javascript

import DashboardListPage from "@/basic/dashboard/ListPage.vue";

export default {
name: "DashboardTags",
components: { DashboardListPage },
};

.. list-table:: DashboardListPage properties
:header-rows: 1

* - Prop
- Description
- Default
- Type
- Required
* - title
- Card title
- None
- String
- True
* - columns
- ``BasicTable`` column definitions
- None
- Array
- True
* - data
- ``BasicTable`` row data
- None
- Array
- True
* - buttons
- Row-action buttons (manage column)
- ``[]``
- Array
- False
* - tableOptions
- Merged onto ``DEFAULT_DASHBOARD_TABLE_OPTIONS`` from ``constants.js``
- ``null`` (use the defaults)
- Object
- False
* - maxTableHeight
- Passed to ``BasicTable``
- ``DASHBOARD_TABLE_HEIGHT`` (``"65vh"``)
- String or Number
- False

.. list-table:: DashboardListPage events & slots
:header-rows: 1

* - Name
- Type
- Description
* - ``@action``
- event
- Forwarded from ``BasicTable``
* - ``#headerActions``
- slot
- Header buttons (``Tags.vue``, ``Projects.vue``, …)
* - ``#afterTable``
- slot
- Extra UI under the table, inside the card body (``Documents.vue``)

Row-button helpers and ``confirmSoftDelete`` live in ``frontend/src/basic/dashboard/actions.js``.
``withSearch()`` lives in ``constants.js``.

**Coordinator**

The coordinator wraps a :ref:`Form <form-section>` inside a modal to **add/edit** backend entries.
Expand All @@ -72,7 +159,7 @@ It pulls field definitions from the Vuex store (``table/<name>/getFields``; see

.. code-block:: javascript

import BasicCoordinator from "@/basic/Coordinator.vue";
import BasicCoordinator from "@/basic/dashboard/Coordinator.vue";

export default {
components: { BasicCoordinator },
Expand Down
14 changes: 14 additions & 0 deletions docs/source/for_developers/frontend/basic/table.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@ The dictionary contains the following keys:

-----

**Row action buttons**

``BasicTable`` accepts a ``buttons`` array. That becomes the manage column. For each row,
``getFilteredButtons`` in ``Table.vue`` decides which buttons to show.

Each button may include ``filter``: an array of ``{ key, value }`` checks against that row.
Default ``filterMode`` is ``or`` (show if any check matches). Set ``filterMode: "and"`` when
every check must match.

On a dashboard list page, build this array with ``dashboardRowAction`` / ``dashboardRowButton``
from ``frontend/src/basic/dashboard/actions.js``. See :doc:`../components/dashboard`.

-----

**Pagination**

Pagination is handled by the ``BasicTablePagination`` component.
Expand Down
127 changes: 124 additions & 3 deletions docs/source/for_developers/frontend/components/dashboard.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Adding a New Dashboard Component
Let's assume we want to add a new Dashboard component ``MyAnnotations``. To add the component, we need to

1. Add a new :doc:`DB migration <for_developers/backend/database>` extending the navigation database.
2. Create a new frontend component in the ``frontend/src/components`` directory
2. Create a new frontend component in the ``frontend/src/components/dashboard`` directory

Please refer to the :doc:`database <for_developers/backend/database>` chapter for a detailed explanation of the
migration functionality. Here, we only cover the necessary commands and code snippets to add a dashboard component.
Expand Down Expand Up @@ -79,7 +79,7 @@ specific element again on ``down``:
}
};

Finally, you add a new Vue component ``MyAnnotations.vue`` to the ``frontend/src/components`` directory. For testing,
Finally, you add a new Vue component ``MyAnnotations.vue`` to the ``frontend/src/components/dashboard`` directory. For testing,
we set this to an empty component showing just "Hello World!":

.. code-block:: vue-js
Expand All @@ -95,6 +95,126 @@ That's it -- to load the new component in the frontend, you first need to stop t
start it up again. Now you should see a nav element in the dashboard sidebar, which shows upon selection an empty
component with just the words "Hello World!".

List dashboard pages
--------------------
A list-style management screen is a **card + table + row buttons**. Do not copy a raw ``Card`` / ``BasicTable``
shell. Use the shared list-page foundation under ``frontend/src/basic/dashboard/``
(component reference: :doc:`../basic/dashboard`):

* ``ListPage.vue`` (``DashboardListPage``) — card, header slot, table, default height
* ``constants.js`` — ``DEFAULT_DASHBOARD_TABLE_OPTIONS``, ``withSearch()``, ``DASHBOARD_TABLE_HEIGHT``
* ``actions.js`` — row-button catalog, ``DASHBOARD_BADGES``, ``confirmSoftDelete()``

Copy a small existing page such as ``Tags.vue`` or ``Projects.vue`` and change columns, data, and buttons.
Do not copy ``Log.vue`` (server-side pagination), ``Study.vue``, ``Submissions.vue``,
``SessionOverview.vue``, ``Settings.vue``, ``AdminTools.vue``, or ``UserStatistics.vue`` — those layouts are
specialized.

**Template**

.. code-block:: html

<template>
<DashboardListPage
title="Tag Sets"
:columns="columns"
:data="tagSets"
:buttons="buttons"
@action="action"
>
<template #headerActions>
<BasicButton
class="btn-primary btn-sm"
title="Add"
icon="plus"
@click="$refs.tagSetModal.open(0)"
/>
</template>
</DashboardListPage>
<TagSetModal ref="tagSetModal" />
<ConfirmModal ref="confirm" />
</template>

``DashboardListPage`` already applies ``DEFAULT_DASHBOARD_TABLE_OPTIONS`` and
``DASHBOARD_TABLE_HEIGHT`` (see ``ListPage.vue``). Pass ``:table-options="withSearch()"``
only when the table needs search. Put page modals as siblings of ``DashboardListPage``,
as ``Tags.vue`` and ``Projects.vue`` do. Extra UI under the table goes in ``#afterTable``
(``Documents.vue``). Register ``BasicButton``, ``ConfirmModal``, and the page modal the same
way ``Tags.vue`` does.

**Script imports**

.. code-block:: javascript

import DashboardListPage from "@/basic/dashboard/ListPage.vue";
import { dashboardRowAction, confirmSoftDelete } from "@/basic/dashboard/actions.js";

Also from those files when needed: ``withSearch`` from ``constants.js``;
``dashboardRowButton`` and ``DASHBOARD_BADGES`` from ``actions.js``.

Declare ``subscribeTable: ["<table>"]`` for every table whose getter this page reads
(see :doc:`../plugins`). Read rows with ``this.$store.getters["table/<table>/getAll"]``
or ``getFiltered`` (see :doc:`../vuex_store`).

**Row buttons**

* ``dashboardRowAction("edit", { title, action, filter, stats })`` — catalog name
(``edit``, ``delete``, ``copy``, ``share``, ``download``, …) so icons and colors stay consistent.
Unknown catalog names throw (see ``dashboardRowAction`` in ``actions.js``).
* ``dashboardRowButton("upload", { title, action, ... })`` — first argument is a Bootstrap icon
name, not a catalog key. Use this for page-only icons (Assignments metadata upload uses
``"upload"``).
* Handle ``@action``. Existing pages name the handler ``action`` (Tags, Projects) or
``chooseAction`` (Users, Workflows). Switch on ``data.action``.

**When a button should appear only on some rows**

``filter`` is a list of ``{ key, value }`` checks against that row. One check is enough by itself.
With two or more checks, decide OR vs AND:

* Default is **OR** — show the button if **any** check matches. Use this when the same field has
two allowed values (for example uploaded by me **or** uploaded by nobody).
* ``filterMode: "and"`` — show the button only if **all** checks match. Use this when two different
fields must be true together.

Share on a private row you own needs **AND**. Without it, Share also appears on someone else's
private row (``public === false``) and on your already-public row (``userId`` matches):

.. code-block:: javascript

dashboardRowAction("share", {
title: "Share tag set",
action: "publishTagSet",
filter: [
{ key: "public", value: false },
{ key: "userId", value: this.userId },
],
filterMode: "and",
})

**Soft delete**

If the row is removed with ``appDataUpdate`` and ``deleted: true``, call ``confirmSoftDelete`` from
``actions.js`` (confirm dialog + socket + error toast). Keep a dedicated socket such as
``templateDelete`` or ``submissionDelete`` when that is how the backend deletes the row.

.. code-block:: javascript

confirmSoftDelete(
{
confirmRef: this.$refs.confirm,
socket: this.$socket,
eventBus: this.eventBus,
},
{
table: "tag_set",
id: row.id,
title: "Delete Tagset",
message: "Do you really want to delete the Tagset?",
failTitle: "TagSet delete failed",
}
);

Populating a Dashboard Component
------------------------------------
Populating a dashboard component usually means (A) loading data via the websocket interface and (B) visualizing it
Expand All @@ -111,5 +231,6 @@ available for visualization in the frontend, but provide only a conceptual overv
Table
-----
The table is the best way to visualize many rows of data.
We recommend to use the basic table component :doc:`Table <basic/table>` for this purpose.
For a full dashboard list page, wrap it with ``DashboardListPage`` as described above.
``DashboardListPage`` uses the basic table component :doc:`Table <../basic/table>`.

Loading
Loading