Skip to content

Commit

Permalink
Implemented selectable rows (#73)
Browse files Browse the repository at this point in the history
* Implemented selectable rows

* Implemented selectable rows

* Hiding bulk action's div if selectable rows is false

* Move fetching selectable rows props to the table structure fetch

* On mount add parallel execution for table structure and data

* Add trash icon in Delete Selected button
  • Loading branch information
Berat-Dzhevdetov authored and cvetty committed Dec 13, 2024
1 parent 3f423d0 commit bda6685
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 15 deletions.
8 changes: 6 additions & 2 deletions krait-ui/src/actions/delete-record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import BaseAction from './base-action';

interface IFetchRecordsOptions {
url: string;
body: any;
}

interface IFetchRecordsResult {
Expand All @@ -20,9 +21,12 @@ export default class DeleteRecord extends BaseAction<
IFetchRecordsOptions,
IFetchRecordsResult
> {
async process({ url }: IFetchRecordsOptions): Promise<IFetchRecordsResult> {
async process({
url,
body = null,
}: IFetchRecordsOptions): Promise<IFetchRecordsResult> {
this.context.isLoading.value = true;
await ApiClient.fetch(url, null, 'DELETE');
await ApiClient.fetch(url, body, 'DELETE');

this.context.isLoading.value = false;

Expand Down
9 changes: 5 additions & 4 deletions krait-ui/src/actions/fetch-records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ export default class FetchRecords extends BaseAction<
* Sets the filters query to the request url using
* the passed form.
*
* @param {string} formSelector - The query selector for filter form element.
* @param {URL} url - The request url
* @private
*/
private setFilters(formSelector: string, url: URL): void {
const form = document.querySelector<HTMLFormElement>(formSelector);
private setFilters(url: URL): void {
const form = document.querySelector<HTMLFormElement>(
this.tableProps.filtersForm,
);

if (!form) {
throw new Error('No filters form found.');
Expand Down Expand Up @@ -144,7 +145,7 @@ export default class FetchRecords extends BaseAction<
const url = Config.tablesUrl;
url.pathname = `${url.pathname}/${this.tableName}`;
if (this.tableProps?.filtersForm) {
this.setFilters(this.tableProps.filtersForm, url);
this.setFilters(url);
}
this.setSorting(this.context.sorting, url);
this.setPagination(this.context.pagination, url);
Expand Down
20 changes: 18 additions & 2 deletions krait-ui/src/actions/fetch-structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,29 @@ export default class FetchStructure extends BaseAction<
private async parseResponse(response: Response) {
const { data } = await response.json();

const { columns, preview_configuration } =
data as Responses.ITableStructureResponse;
const {
columns,
preview_configuration,
selectable_rows,
bulk_action_links,
} = data as Responses.ITableStructureResponse;

this.setColumns(columns);
this.setSorting(preview_configuration);
this.setVisibleColumns(preview_configuration);
this.setColumnWidth(preview_configuration);
this.setIsSelectableRows(selectable_rows);
this.setBulkActionLinks(bulk_action_links);
}

private setBulkActionLinks(
bulkActions: Responses.ITableStructureResponse['bulk_action_links'],
) {
this.context.bulkActionLinks.value = bulkActions;
}

private setIsSelectableRows(value: boolean) {
this.context.isSelectableRows.value = value;
}

private setColumns(columns: IColumn[]) {
Expand Down
48 changes: 48 additions & 0 deletions krait-ui/src/components/bulk-action-links/BulkActionLinksList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script setup lang="ts">
import {useConfirmation, useDispatcher, useTable} from '~/mixins';
import {DeleteRecord, FetchRecords} from '~/actions';
import {Trash} from "@components/icons";
const props = defineProps({
tableName: {
type: String,
required: true,
},
});
const {isLoading, bulkActionLinks, selectedRows, isSelectableRows} = useTable(
props.tableName,
);
const {dispatch} = useDispatcher(props.tableName);
const {ask} = useConfirmation();
const bulkDelete = async () => {
if (selectedRows.value.length <= 0) return;
const isConfirmed = await ask(
'Are you sure that you want to delete those record?',
);
if (isConfirmed && bulkActionLinks.value.delete) {
await dispatch<DeleteRecord>(DeleteRecord, {
url: bulkActionLinks.value.delete,
body: {data: selectedRows.value},
});
await dispatch<FetchRecords>(FetchRecords, {});
}
};
</script>

<template>
<div v-if="isSelectableRows">
<button
v-if="bulkActionLinks?.delete"
class="btn btn-danger d-flex gap-1"
:disabled="selectedRows.length == 0 || isLoading"
@click="bulkDelete"
>
<Trash width="18"/>
Delete selected
</button>
</div>
</template>
1 change: 1 addition & 0 deletions krait-ui/src/components/bulk-action-links/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as BulkActionLinksList } from './BulkActionLinksList.vue';
40 changes: 34 additions & 6 deletions krait-ui/src/components/dynamic-table/DynamicTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FetchRecords, FetchStructure } from '~/actions';
import { RowActionButtons } from '@components/row-action-buttons';
import ForbiddenScreen from './ForbiddenScreen.vue';
import ConfirmationDialog from '@components/confirmation-dialog/ConfirmationDialog.vue';
import { BulkActionLinksList } from '@components/bulk-action-links';
import { Table } from '~/types';
const props = defineProps({
Expand All @@ -33,9 +34,15 @@ const props = defineProps({
},
});
const { columns, isLoading, records, visibleColumns, isAuthorized } = useTable(
props.apiEndpoint,
);
const {
columns,
isLoading,
records,
visibleColumns,
isAuthorized,
isSelectableRows,
selectedRows,
} = useTable(props.apiEndpoint);
const configuration = useTableConfiguration(
props.apiEndpoint,
props as Table.ITableConfiguration,
Expand Down Expand Up @@ -70,17 +77,30 @@ const refreshTable = async () => {
await dispatch<FetchRecords>(FetchRecords, {});
};
const toggleRowSelection = (recordUuid: string) => {
if (selectedRows.value.includes(recordUuid)) {
selectedRows.value = selectedRows.value.filter(
(uuid) => uuid !== recordUuid,
);
} else {
selectedRows.value.push(recordUuid);
}
};
onMounted(async () => {
await dispatch<FetchStructure>(FetchStructure, {});
await dispatch<FetchRecords>(FetchRecords, {});
const fetchStructurePromise = dispatch<FetchStructure>(FetchStructure, {});
const fetchRecordsPromise = dispatch<FetchRecords>(FetchRecords, {});
await Promise.all([fetchStructurePromise, fetchRecordsPromise]);
initFiltersListener();
});
</script>

<template>
<ToastsList />
<ConfirmationDialog />
<div class="d-flex justify-content-end mb-3" v-if="isAuthorized">
<div class="d-flex justify-content-end mb-3 gap-2" v-if="isAuthorized">
<BulkActionLinksList :table-name="apiEndpoint"></BulkActionLinksList>
<ColumnsSelectionDropdown
:table-name="apiEndpoint"
></ColumnsSelectionDropdown>
Expand All @@ -106,6 +126,14 @@ onMounted(async () => {
<tbody>
<template v-for="record in records" :key="record.uuid">
<tr>
<td v-if="isSelectableRows">
<input
type="checkbox"
:value="record.uuid"
:checked="selectedRows.includes(record.uuid)"
@change="toggleRowSelection(record.uuid)"
/>
</td>
<td
v-for="column in columns"
:key="column.name"
Expand Down
53 changes: 53 additions & 0 deletions krait-ui/src/components/select-all-checkbox/SelectAllCheckbox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useTable } from '~/mixins';
const props = defineProps({
tableName: {
type: String,
required: true,
},
});
const { selectedRows, records } = useTable(props.tableName);
const checkBoxInput = ref<HTMLInputElement | null>(null);
const isAllSelected = ref(false);
const isIndeterminate = ref(false);
const toggleSelectAll = () => {
if (isIndeterminate.value || !isAllSelected.value) {
selectedRows.value = records.value.map((record) => record.uuid);
} else {
selectedRows.value = [];
}
};
watch(
selectedRows,
(newSelectedRows) => {
if (checkBoxInput.value) {
if (newSelectedRows.length === records.value.length) {
isAllSelected.value = true;
isIndeterminate.value = false;
checkBoxInput.value.indeterminate = false;
} else {
isAllSelected.value = false;
isIndeterminate.value = newSelectedRows.length > 0;
checkBoxInput.value.indeterminate = isIndeterminate.value;
}
}
},
{
deep: true,
},
);
</script>

<template>
<input
type="checkbox"
@click="toggleSelectAll"
:checked="isAllSelected"
ref="checkBoxInput"
/>
</template>
1 change: 1 addition & 0 deletions krait-ui/src/components/select-all-checkbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as SelectAllCheckbox } from './SelectAllCheckbox.vue';
13 changes: 12 additions & 1 deletion krait-ui/src/components/thead/THead.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Draggable from 'vuedraggable';
import { useDispatcher, useTable } from '~/mixins';
import { DynamicColumn } from '@components/dynamic-column';
import { ResizeColumn, SortColumn, SaveColumnsOrder } from '~/actions';
import { SelectAllCheckbox } from '@components/select-all-checkbox';
const props = defineProps({
tableName: {
Expand All @@ -19,7 +20,9 @@ const props = defineProps({
const emit = defineEmits(['refreshTable']);
const { columns, visibleColumns, sorting } = useTable(props.tableName);
const { columns, visibleColumns, sorting, isSelectableRows } = useTable(
props.tableName,
);
const { dispatch } = useDispatcher(props.tableName);
const dragging = ref<boolean>(false);
Expand Down Expand Up @@ -51,6 +54,14 @@ const sortColumn = async (name: string, direction: string) => {
<template>
<thead>
<tr>
<th
class="text-nowrap"
scope="col"
:style="`width: 35px`"
v-if="isSelectableRows"
>
<SelectAllCheckbox :table-name="props.tableName"></SelectAllCheckbox>
</th>
<draggable
v-model="columns"
tag="transition-group"
Expand Down
6 changes: 6 additions & 0 deletions krait-ui/src/mixins/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const getState = (): Table.ITableContext => {
const isLoading = ref<boolean>(false);
const links = ref<Responses.ILinks>({});
const isAuthorized = ref<boolean>(true);
const isSelectableRows = ref<boolean>(false);
const selectedRows = ref<Array<string | number>>([]);
const bulkActionLinks = ref<{ [key: string]: string }>({});

return {
columns,
Expand All @@ -39,6 +42,9 @@ const getState = (): Table.ITableContext => {
records,
links,
isAuthorized,
isSelectableRows,
selectedRows,
bulkActionLinks,
};
};

Expand Down
2 changes: 2 additions & 0 deletions krait-ui/src/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ export interface ITableDataResponse {
export interface ITableStructureResponse {
preview_configuration: IPreviewConfiguration | null;
columns: IColumn[];
selectable_rows: boolean;
bulk_action_links: { [key: string]: string };
}
3 changes: 3 additions & 0 deletions krait-ui/src/types/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export interface ITableContext {
visibleColumns: Ref<UnwrapRef<string[]>>;
isAuthorized: Ref<boolean>;
queryParameters: Ref<IQueryParameters>;
isSelectableRows: Ref<boolean>;
bulkActionLinks: Ref<UnwrapRef<{ [key: string]: string }>>;
selectedRows: Ref<string[] | number[]>;
}

export interface ITableConfiguration {
Expand Down
2 changes: 2 additions & 0 deletions krait/src/Http/Resources/TableStructureCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public function toArray(Request $request)
return [
'preview_configuration' => $previewConfiguration ? new KraitPreviewConfigurationResource($previewConfiguration) : null,
'columns' => $this->getColumns($previewConfiguration),
'selectable_rows' => $this->table->isSelectableRows(),
'bulk_action_links' => $this->table->bulkActionLinks(),
];
}

Expand Down
18 changes: 18 additions & 0 deletions krait/src/Tables/BaseTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ public function hasColumn(string $name): bool
return isset($columns[$name]);
}

/**
* Returns if selectable rows Defaults to false.
*/
public function isSelectableRows(): bool
{
return false;
}

/**
* Returns a Laravel Facade of the Table class.
*
Expand Down Expand Up @@ -286,4 +294,14 @@ public function actionLinks(mixed $resource): array
{
return [];
}

/**
* Returns the bulk action links for table
*
* @return array - the bulk action links
*/
public function bulkActionLinks(): array
{
return [];
}
}

0 comments on commit bda6685

Please sign in to comment.