add table actions to datatable, client page, start writing db method for clients

This commit is contained in:
Casey 2025-11-11 09:50:23 -06:00
parent a67e86af44
commit df1df3f882
9 changed files with 1844 additions and 194 deletions

View file

@ -11,6 +11,7 @@ A feature-rich data table component built with PrimeVue's DataTable. This compon
<DataTable
:columns="tableColumns"
:data="tableData"
:tableActions="tableActions"
table-name="my-table"
@row-click="handleRowClick"
/>
@ -41,6 +42,32 @@ const tableData = ref([
{ id: 2, name: "Jane Smith", status: "in progress" },
]);
const tableActions = ref([
{
label: "Add Item",
action: () => console.log("Add clicked"),
icon: "pi pi-plus",
style: "primary",
// Global action - always available
},
{
label: "View Details",
action: (rowData) => console.log("View:", rowData),
icon: "pi pi-eye",
style: "info",
requiresSelection: true,
// Single selection action - enabled when exactly one row selected
},
{
label: "Edit",
action: (rowData) => console.log("Edit:", rowData),
icon: "pi pi-pencil",
style: "secondary",
rowAction: true,
// Row action - appears in each row's actions column
},
]);
const handleRowClick = (event) => {
console.log("Row clicked:", event.data);
};
@ -85,6 +112,12 @@ const handleRowClick = (event) => {
- **Type:** `Object`
- **Default:** `{ global: { value: null, matchMode: FilterMatchMode.CONTAINS } }`
### `tableActions` (Array)
- **Description:** Array of action objects that define interactive buttons for the table. Actions can be global (always available), single-selection (enabled when exactly one row is selected), row-specific (displayed per row), or bulk (for multiple selected rows).
- **Type:** `Array<Object>`
- **Default:** `[]`
## Server-Side Pagination & Lazy Loading
When `lazy` is set to `true`, the DataTable operates in server-side mode with the following features:
@ -159,6 +192,179 @@ Renders values as clickable buttons:
}
```
## Table Actions Configuration
Table actions allow you to add interactive buttons to your DataTable. Actions can be either global (displayed above the table) or row-specific (displayed in an actions column).
### Action Object Properties
Each action object in the `tableActions` array supports the following properties:
#### Basic Properties
- **`label`** (String, required) - Display text for the button
- **`action`** (Function, required) - Function to execute when button is clicked
- **`icon`** (String, optional) - PrimeVue icon class (e.g., 'pi pi-plus')
- **`style`** (String, optional) - Button severity: 'primary', 'secondary', 'success', 'info', 'warning', 'danger'
- **`size`** (String, optional) - Button size: 'small', 'normal', 'large'
- **`requiresSelection`** (Boolean, default: false) - When true, action appears above table but is only enabled when exactly one row is selected
- **`requiresMultipleSelection`** (Boolean, default: false) - Determines if action is for bulk operations on selected rows
- **`rowAction`** (Boolean, default: false) - When true, action appears in each row's actions column
- **`layout`** (Object, optional) - Layout configuration for action positioning and styling
#### Layout Configuration
The `layout` property allows you to control where and how actions are displayed:
##### For Top-Level Actions (Global and Single Selection)
```javascript
layout: {
position: "left" | "center" | "right", // Where to position in action bar
variant: "filled" | "outlined" | "text" // Visual style variant
}
```
##### For Row Actions
```javascript
layout: {
priority: "primary" | "secondary" | "dropdown", // Display priority in row
variant: "outlined" | "text" | "compact" | "icon-only" // Visual style
}
```
##### For Bulk Actions
```javascript
layout: {
position: "left" | "center" | "right", // Where to position in bulk action bar
variant: "filled" | "outlined" | "text" // Visual style variant
}
```
#### Action Types
##### Global Actions (default behavior)
Global actions are displayed above the table and are always available:
```javascript
{
label: "Add New Item",
action: () => {
// Global action - no row data
console.log("Opening create modal");
},
icon: "pi pi-plus",
style: "primary"
// No requiresSelection, requiresMultipleSelection, or rowAction properties
}
```
##### Single Selection Actions (`requiresSelection: true`)
Single selection actions are displayed above the table but are only enabled when exactly one row is selected. They receive the selected row data as a parameter:
```javascript
{
label: "View Details",
action: (rowData) => {
// Single selection action - receives selected row data
console.log("Viewing:", rowData.name);
router.push(`/items/${rowData.id}`);
},
icon: "pi pi-eye",
style: "info",
requiresSelection: true
}
```
##### Row Actions (`rowAction: true`)
Row actions are displayed in an "Actions" column for each row and receive that row's data as a parameter:
```javascript
{
label: "Edit",
action: (rowData) => {
// Row action - receives individual row data
console.log("Editing:", rowData.name);
openEditModal(rowData);
},
icon: "pi pi-pencil",
style: "secondary",
rowAction: true
}
```
##### Bulk Actions (`requiresMultipleSelection: true`)
Bulk actions are displayed above the table when rows are selected and receive an array of selected row data:
```javascript
{
label: "Delete Selected",
action: (selectedRows) => {
// Bulk action - receives array of selected row data
console.log("Deleting:", selectedRows.length, "items");
selectedRows.forEach(row => deleteItem(row.id));
},
icon: "pi pi-trash",
style: "danger",
requiresMultipleSelection: true
}
```
### Example Table Actions Configuration
```javascript
const tableActions = [
// Global action - shows above table, always available
{
label: "Add Client",
action: () => modalStore.openModal("createClient"),
icon: "pi pi-plus",
style: "primary",
},
// Single selection action - shows above table, enabled when exactly one row selected
{
label: "View Details",
action: (rowData) => router.push(`/clients/${rowData.id}`),
icon: "pi pi-eye",
style: "info",
requiresSelection: true,
},
// Bulk action - shows when rows selected
{
label: "Delete Selected",
action: (selectedRows) => {
if (confirm(`Delete ${selectedRows.length} clients?`)) {
selectedRows.forEach((row) => deleteClient(row.id));
}
},
icon: "pi pi-trash",
style: "danger",
requiresMultipleSelection: true,
},
// Row actions - show in each row's actions column
{
label: "Edit",
action: (rowData) => editClient(rowData),
icon: "pi pi-pencil",
style: "secondary",
rowAction: true,
},
{
label: "Quick View",
action: (rowData) => showQuickPreview(rowData),
icon: "pi pi-search",
style: "info",
rowAction: true,
},
];
```
## Events
### `rowClick`
@ -253,6 +459,17 @@ const handleLazyLoad = async (event) => {
- **Automatic filter initialization** on component mount
- **Cross-component filter synchronization**
### Table Actions
- **Global actions** displayed above the table for general operations
- **Row-specific actions** in dedicated actions column with row data access
- **Bulk actions** for selected rows with multi-selection support
- **Customizable button styles** with PrimeVue severity levels
- **Icon support** using PrimeVue icons
- **Automatic action handling** with error catching
- **Disabled state** during loading operations
- **Dynamic bulk action visibility** based on row selection
## Usage Examples
### Server-Side Paginated Table (Recommended for Large Datasets)
@ -324,6 +541,126 @@ const handleLazyLoad = async (event) => {
</template>
```
### Interactive Table with Actions
```vue
<script setup>
import { useRouter } from "vue-router";
import { useModalStore } from "./stores/modal";
const router = useRouter();
const modalStore = useModalStore();
const columns = [
{ fieldName: "name", label: "Name", sortable: true, filterable: true },
{ fieldName: "status", label: "Status", type: "status", sortable: true },
{ fieldName: "email", label: "Email", filterable: true },
];
const data = [
{ id: 1, name: "John Doe", status: "completed", email: "john@example.com" },
{
id: 2,
name: "Jane Smith",
status: "in progress",
email: "jane@example.com",
},
];
const tableActions = [
// Global action
{
label: "Add User",
action: () => {
modalStore.openModal("createUser");
},
icon: "pi pi-plus",
style: "primary",
},
// Single selection action
{
label: "View Details",
action: (rowData) => {
router.push(`/users/${rowData.id}`);
},
icon: "pi pi-eye",
style: "info",
requiresSelection: true,
},
// Bulk actions
{
label: "Export Selected",
action: (selectedRows) => {
exportUsers(selectedRows);
},
icon: "pi pi-download",
style: "success",
requiresMultipleSelection: true,
},
{
label: "Delete Selected",
action: (selectedRows) => {
if (confirm(`Delete ${selectedRows.length} users?`)) {
bulkDeleteUsers(selectedRows.map((row) => row.id));
}
},
icon: "pi pi-trash",
style: "danger",
requiresMultipleSelection: true,
},
// Row actions
{
label: "Edit",
action: (rowData) => {
modalStore.openModal("editUser", rowData);
},
icon: "pi pi-pencil",
style: "secondary",
rowAction: true,
},
{
label: "Quick Actions",
action: (rowData) => {
showQuickActionsMenu(rowData);
},
icon: "pi pi-ellipsis-v",
style: "info",
rowAction: true,
},
];
const deleteUser = async (userId) => {
// API call to delete user
await Api.deleteUser(userId);
};
const bulkDeleteUsers = async (userIds) => {
// API call to delete multiple users
await Api.bulkDeleteUsers(userIds);
};
const exportUsers = (users) => {
// Export selected users to CSV/Excel
const csv = generateCSV(users);
downloadFile(csv, "users.csv");
};
const refreshData = () => {
// Refresh table data
location.reload();
};
</script>
<template>
<DataTable
:columns="columns"
:data="data"
:tableActions="tableActions"
table-name="users-table"
/>
</template>
```
### Basic Client-Side Table
```vue
@ -427,6 +764,118 @@ const customFilters = {
</template>
```
### Layout-Aware Actions Example
```vue
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useModalStore } from '@/stores/modal'
const router = useRouter()
const modalStore = useModalStore()
const columns = [
{ fieldName: 'name', label: 'Client Name', sortable: true, filterable: true },
{ fieldName: 'email', label: 'Email', filterable: true },
{ fieldName: 'status', label: 'Status', type: 'status', sortable: true }
]
const data = ref([
{ id: 1, name: 'Acme Corp', email: 'contact@acme.com', status: 'completed' },
{ id: 2, name: 'Tech Solutions', email: 'info@tech.com', status: 'in progress' }
])
const tableActions = [
// Left-positioned action with filled style
{
label: 'Quick Export',
icon: 'pi pi-download',
action: () => exportAllClients(),
severity: 'success',
layout: { position: 'left', variant: 'outlined' }
},
// Center-positioned bulk action
{
label: 'Archive Selected',
icon: 'pi pi-archive',
action: (selectedRows) => archiveClients(selectedRows),
severity: 'warning',
requiresMultipleSelection: true,
layout: { position: 'center', variant: 'outlined' }
},
// Right-positioned main action
{
label: 'Create Client',
icon: 'pi pi-plus',
action: () => modalStore.openModal('createClient'),
severity: 'info',
layout: { position: 'right', variant: 'filled' }
},
// Single selection action
{
label: 'Edit Details',
icon: 'pi pi-pencil',
action: (rowData) => router.push(`/clients/${rowData.id}/edit`),
severity: 'secondary',
requiresSelection: true,
layout: { position: 'left', variant: 'text' }
},
// Primary row action - most important
{
label: 'View',
icon: 'pi pi-eye',
action: (rowData) => router.push(`/clients/${rowData.id}`),
severity: 'info',
rowAction: true,
layout: { priority: 'primary', variant: 'outlined' }
},
// Secondary row action - less important
{
label: 'Contact',
icon: 'pi pi-phone',
action: (rowData) => initiateContact(rowData),
severity: 'success',
rowAction: true,
layout: { priority: 'secondary', variant: 'text' }
},
// Dropdown row action - additional options
{
label: 'More',
icon: 'pi pi-ellipsis-v',
action: (rowData) => showMoreOptions(rowData),
rowAction: true,
layout: { priority: 'dropdown', variant: 'icon-only' }
}
]
const exportAllClients = () => {
// Export logic
}
const archiveClients = (clients) => {
// Archive logic
}
const initiateContact = (client) => {
// Contact logic
}
const showMoreOptions = (client) => {
// More options logic
}
</script>
<template>
<DataTable
:columns="columns"
:data="data"
:tableActions="tableActions"
table-name="clients-with-layout"
/>
</template>
```
## Store Integration
The component integrates with a Pinia store (`useFiltersStore`) for persistent filter state: