Skip to content

Commit 3063063

Browse files
committed
feat: generic table and folder table without search
1 parent e9209e4 commit 3063063

6 files changed

Lines changed: 100 additions & 76 deletions

File tree

resources/js/components/cards/FolderCard.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { computed } from 'vue';
99
1010
import RelativeHoverCard from '@/components/cards/RelativeHoverCard.vue';
1111
import ButtonCorner from '@/components/inputs/ButtonCorner.vue';
12+
import ChipTag from '@/components/labels/ChipTag.vue';
1213
1314
import CircumFolderOn from '~icons/circum/folder-on';
1415
import CircumShare1 from '~icons/circum/share-1';
@@ -50,7 +51,7 @@ const contextMenuItems = computed(() => {
5051
<template #trigger>
5152
<RouterLink
5253
:to="`/${data.path}`"
53-
class="text-left relative flex flex-col sm:gap-4 sm:flex-row flex-wrap rounded-lg sm:p-3 dark:bg-primary-dark-800/70 bg-primary-800 dark:hover:bg-primary-dark-600 hover:bg-gray-200 dark:text-white shadow w-full group cursor-pointer divide-gray-300 dark:divide-gray-400"
54+
class="text-left relative flex flex-col sm:flex-row flex-wrap rounded-lg sm:p-3 dark:bg-primary-dark-800/70 bg-primary-800 dark:hover:bg-primary-dark-600 hover:bg-gray-200 dark:text-white shadow w-full group cursor-pointer divide-gray-300 dark:divide-gray-400"
5455
@contextmenu="
5556
(e: any) => {
5657
setContextMenu(e, { items: contextMenuItems });
@@ -60,7 +61,7 @@ const contextMenuItems = computed(() => {
6061
<img
6162
:src="handleStorageURL(data.series?.thumbnail_url) ?? '/storage/thumbnails/default.webp'"
6263
alt="Folder Thumbnail"
63-
class="hidden xs:block lg:hidden max-h-16 sm:w-12 aspect-square object-cover shadow-md rounded-t-lg sm:rounded-sm"
64+
class="hidden xs:block lg:hidden max-h-16 sm:w-12 aspect-square object-cover shadow-md rounded-t-lg sm:rounded-sm sm:me-4"
6465
/>
6566
<span class="w-full flex-1 text-left relative flex flex-col gap-4 lg:gap-2 sm:flex-row flex-wrap p-3 sm:p-0">
6667
<section class="flex justify-between gap-4 w-full items-center">
@@ -103,6 +104,18 @@ const contextMenuItems = computed(() => {
103104
</h3>
104105
</section>
105106
</span>
107+
<section
108+
v-if="props.data.series?.folder_tags?.length"
109+
class="flex gap-1 p-3 sm:p-0 pt-0 transition-[max-height] sm:max-h-[0px] md:group-hover:max-h-[30px] w-full overflow-hidden flex-wrap"
110+
title="Tags"
111+
>
112+
<ChipTag
113+
v-for="(tag, index) in props.data.series.folder_tags"
114+
v-bind:key="index"
115+
:label="tag.name"
116+
:colour="'bg-neutral-200 leading-none text-neutral-500 shadow dark:bg-neutral-900 hover:bg-violet-600 hover:text-neutral-50 hover:dark:bg-violet-600/90 !max-h-[22px] sm:mt-2'"
117+
/>
118+
</section>
106119
</RouterLink>
107120
</template>
108121
</RelativeHoverCard>

resources/js/components/dashboard/DashboardLibraryFolders.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const handleStartScan = async () => {
8888
}
8989
};
9090
91-
const handleFolderAction = (e: Event, id: number, action: 'edit' | 'share' = 'edit') => {
91+
const handleFolderAction = (id: number, action: 'edit' | 'share' = 'edit') => {
9292
let folder = stateLibraryFolders.value?.find((folder: FolderResource) => folder.id === id);
9393
9494
if (folder) cachedFolder.value = folder;

resources/js/components/panels/VideoSidebar.vue

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import RecordCard from '@/components/cards/RecordCard.vue';
1313
import EditFolder from '@/components/forms/EditFolder.vue';
1414
import ModalBase from '@/components/pinesUI/ModalBase.vue';
1515
import useModal from '@/composables/useModal';
16+
import TableBase from '@/components/table/TableBase.vue';
1617
1718
const editFolderModal = useModal({ title: 'Edit Folder Details', submitText: 'Submit Details' });
1819
const shareModal = useModal({ title: 'Share Video' });
@@ -41,7 +42,7 @@ const handleFolderAction = (id: number, action: 'edit' | 'share' = 'edit') => {
4142
cachedFolder.value = folder;
4243
if (action === 'edit') editFolderModal.toggleModal();
4344
else {
44-
shareLink.value = window.location.origin + '/' + folder.path;
45+
shareLink.value = encodeURI(window.location.origin + '/' + folder.path);
4546
shareModal.toggleModal(true);
4647
}
4748
};
@@ -67,13 +68,18 @@ watch(
6768
</div>
6869

6970
<section v-if="selectedSideBar === 'folders'" id="list-content-folders" class="flex gap-2 flex-wrap">
70-
<FolderCard
71-
v-for="folder in stateDirectory.folders"
72-
:key="folder.id"
73-
:data="folder"
74-
:categoryName="stateDirectory.name"
75-
:stateFolderName="stateFolder?.name"
76-
@clickAction="handleFolderAction"
71+
<TableBase
72+
:data="stateDirectory.folders"
73+
:row="FolderCard"
74+
:clickAction="handleFolderAction"
75+
:useToolbar="false"
76+
:startAscending="true"
77+
:row-attributes="{
78+
categoryName: stateDirectory.name,
79+
stateFolderName: stateFolder?.name,
80+
}"
81+
:items-per-page="10"
82+
:pagination-class="'!justify-center !flex-col-reverse'"
7783
/>
7884
</section>
7985
<section v-if="selectedSideBar === 'history'" id="list-content-history" class="flex gap-2 flex-wrap">

resources/js/components/table/TableBase.vue

Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
<script setup lang="ts">
2-
import { onMounted, ref, type Component } from 'vue';
1+
<script setup lang="ts" generic="T">
2+
import type { SortOption, TableProps } from '@/types/types';
3+
4+
import { onMounted, ref } from 'vue';
35
46
import TextInputLabelled from '@/components/inputs/TextInputLabelled.vue';
57
import TablePagination from '@/components/table/TablePagination.vue';
@@ -12,60 +14,45 @@ import SvgSpinners90RingWithBg from '~icons/svg-spinners/90-ring-with-bg';
1214
import PhSortDescendingLight from '~icons/ph/sort-descending-light';
1315
import PhSortAscendingLight from '~icons/ph/sort-ascending-light';
1416
15-
const props = withDefaults(
16-
defineProps<{
17-
useToolbar?: boolean;
18-
usePagination?: boolean;
19-
useGrid?: string;
20-
data: any[];
21-
row: Component;
22-
rowAttributes?: { [key: string]: any };
23-
loading?: boolean;
24-
clickAction?: any;
25-
otherAction?: any;
26-
sortAction?: any;
27-
sortingOptions?: {
28-
title: string;
29-
value: string;
30-
disabled?: boolean;
31-
}[];
32-
itemsPerPage?: number;
33-
searchQuery?: any;
34-
selectedID?: any;
35-
tableStyles?: string;
36-
startAscending?: boolean;
37-
paginationClass?: string;
38-
}>(),
39-
{
40-
useToolbar: true,
41-
usePagination: true,
42-
itemsPerPage: 12,
43-
selectedID: null,
44-
startAscending: true,
45-
searchQuery: '',
46-
},
47-
);
17+
// 👇 Generic prop types
18+
19+
const props = withDefaults(defineProps<TableProps<T>>(), {
20+
useToolbar: true,
21+
usePagination: true,
22+
itemsPerPage: 5,
23+
selectedID: null,
24+
startAscending: true,
25+
searchQuery: '',
26+
});
27+
28+
const emit = defineEmits<{
29+
(e: 'search', value: string): void;
30+
}>();
31+
4832
const tableData = useTable(props);
4933
const sortAscending = ref(props.startAscending);
5034
const lastSortKey = ref('');
51-
const emit = defineEmits(['search']);
35+
5236
const model = defineModel<string | undefined>({
5337
required: false,
5438
default: undefined,
5539
});
5640
57-
const handleSortChange = (sortKey?: { title?: string; value?: string; disabled?: boolean }) => {
41+
defineSlots<{
42+
row(props: { row: T; index: number; selectedID: any }): any;
43+
}>();
44+
45+
const handleSortChange = (sortKey?: SortOption) => {
5846
if (sortKey?.value) {
5947
lastSortKey.value = sortKey.value;
6048
}
6149
6250
if (!lastSortKey.value) return;
63-
props.sortAction(lastSortKey.value, sortAscending.value ? 1 : -1);
51+
props.sortAction?.(lastSortKey.value as keyof T, sortAscending.value ? 1 : -1);
6452
};
6553
6654
const handleSearch = (event: InputEvent) => {
6755
const target = event.target as HTMLInputElement;
68-
6956
const value = target.value;
7057
7158
if (model.value !== undefined) {
@@ -77,13 +64,13 @@ const handleSearch = (event: InputEvent) => {
7764
};
7865
7966
onMounted(() => {
80-
if (props.useToolbar && props.sortAction) props.sortAction(lastSortKey.value, props.startAscending ? 1 : -1);
67+
if (props.useToolbar && props.sortAction) props.sortAction(lastSortKey.value as keyof T, props.startAscending ? 1 : -1);
8168
});
8269
</script>
8370

8471
<template>
8572
<!-- [&>*:not(:first-child)]:pt-4 -->
86-
<section class="flex flex-col gap-4">
73+
<section class="flex flex-col gap-4 w-full">
8774
<section v-if="props.useToolbar" class="flex justify-center sm:justify-between flex-col sm:flex-row gap-2">
8875
<TextInputLabelled
8976
:value="model ?? tableData.fields.searchQuery"
@@ -94,16 +81,6 @@ onMounted(() => {
9481
title="Search Here"
9582
@input="handleSearch"
9683
/>
97-
<!-- <TextInputLabelled
98-
v-else
99-
v-model="tableData.fields.searchQuery"
100-
:text="'Search:'"
101-
:placeholder="'Enter Search Query...'"
102-
:id="'table-search'"
103-
class="w-full sm:w-80"
104-
title="Search Here"
105-
@input="$emit('search', tableData.fields.searchQuery)"
106-
/> -->
10784
<span class="flex items-end gap-2 flex-wrap">
10885
<div class="flex gap-2 flex-col w-full sm:w-40 flex-1">
10986
<FormInputLabel :field="{ name: 'sort', text: 'Sort by:' }" />
@@ -142,18 +119,20 @@ onMounted(() => {
142119
<p>{{ loading ? '...Loading' : 'No Results' }}</p>
143120
<SvgSpinners90RingWithBg v-show="loading" />
144121
</div>
145-
<component
146-
v-else
147-
:is="props.row"
148-
v-for="(row, index) in tableData.filteredPage"
149-
:key="row?.id ?? index"
150-
:data="row"
151-
:index="index"
152-
:currentID="props.selectedID ?? null"
153-
v-bind="rowAttributes"
154-
@clickAction="(...args: any[]) => props.clickAction?.(row?.id, ...args)"
155-
@otherAction="(...args: any[]) => props.otherAction?.(row?.id, ...args)"
156-
></component>
122+
<template v-else v-for="(row, index) in tableData.filteredPage" :key="row?.id ?? index">
123+
<slot name="row" :row="row" :index="index" :selectedID="props.selectedID">
124+
<component
125+
:is="props.row"
126+
:key="row?.id ?? index"
127+
:data="row"
128+
:index="index"
129+
:currentID="props.selectedID ?? null"
130+
v-bind="rowAttributes"
131+
@clickAction="(...args: any[]) => props.clickAction?.(row?.id, ...args)"
132+
@otherAction="(...args: any[]) => props.otherAction?.(row?.id, ...args)"
133+
></component>
134+
</slot>
135+
</template>
157136
</section>
158137
<!-- <hr class="p-0" /> -->
159138
<TablePagination

resources/js/components/table/TablePagination.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const handleSetPage = async (page: number) => {
3838
</script>
3939

4040
<template>
41-
<div class="flex items-center flex-col sm:flex-row sm:justify-between flex-wrap gap-2 scroll-mb-12" ref="$element">
41+
<div :class="`flex items-center flex-col sm:flex-row sm:justify-between flex-wrap gap-2 scroll-mb-12`" ref="$element">
4242
<p class="text-gray-700 dark:text-neutral-300 line-clamp-1 text-sm">
4343
Showing
4444
<span class="font-medium dark:text-neutral-100">{{ props.listLength ? props.itemsPerPage * (currentPage - 1) + 1 : 0 }}</span>

resources/js/types/types.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { NullConnector, PusherConnector, SocketIoConnector } from 'laravel-echo/dist/connector';
2-
import type { Component } from 'vue';
2+
import type { Component, DefineComponent } from 'vue';
33

44
import type {
55
NullChannel,
@@ -255,3 +255,29 @@ export interface SelectItem {
255255
name: string;
256256
relationships?: any;
257257
}
258+
259+
export declare type SortOption = {
260+
title: string;
261+
value: string;
262+
disabled?: boolean;
263+
};
264+
265+
export interface TableProps<T> {
266+
useToolbar?: boolean;
267+
usePagination?: boolean;
268+
useGrid?: string;
269+
data: T[];
270+
row: DefineComponent<any, any, any> | Component;
271+
rowAttributes?: Record<string, any>;
272+
loading?: boolean;
273+
clickAction?: (id: number, ...args: any[]) => void;
274+
otherAction?: (...args: any[]) => void;
275+
sortAction?: (sortKey: keyof T, direction: 1 | -1) => void;
276+
sortingOptions?: SortOption[];
277+
itemsPerPage?: number;
278+
searchQuery?: string;
279+
selectedID?: number | string | null;
280+
tableStyles?: string;
281+
startAscending?: boolean;
282+
paginationClass?: string;
283+
}

0 commit comments

Comments
 (0)