diff --git a/apps/frontend/src/components/dashboard/table/sort-by.tsx b/apps/frontend/src/components/dashboard/table/sort-by.tsx new file mode 100644 index 00000000..d8496bc0 --- /dev/null +++ b/apps/frontend/src/components/dashboard/table/sort-by.tsx @@ -0,0 +1,42 @@ +import { component$ } from '@builder.io/qwik'; +import { SortOrder } from './table-server-pagination'; +import { LuArrowDown, LuArrowUp } from '@qwikest/icons/lucide'; + +export enum LinkAttribute { + URL = 'url', + Clicks = 'clicks', + CreatedAt = 'createdAt', + ExpirationTime = 'expirationTime', +} + +export interface ISortBy { + attribute: LinkAttribute; + order: SortOrder; +} + +export interface SortByProps { + sortBy: ISortBy; + onChangeAttribute: (e: Event) => void; + toggleOrder: () => void; +} + +export const SortBy = component$(({ sortBy, onChangeAttribute, toggleOrder }) => { + return ( +
+ + +
+ ); +}); diff --git a/apps/frontend/src/routes/dashboard/index.tsx b/apps/frontend/src/routes/dashboard/index.tsx index ef9df565..4ba782f7 100644 --- a/apps/frontend/src/routes/dashboard/index.tsx +++ b/apps/frontend/src/routes/dashboard/index.tsx @@ -1,4 +1,4 @@ -import { component$, useSignal, useVisibleTask$, $ } from '@builder.io/qwik'; +import { component$, useSignal, useVisibleTask$, $, useStore } from '@builder.io/qwik'; import { DocumentHead } from '@builder.io/qwik-city'; import { LinkBlock } from '../../components/dashboard/links/link/link'; import { LINK_MODAL_ID, LinkModal } from '../../components/dashboard/links/link-modal/link-modal'; @@ -11,6 +11,7 @@ import { DELETE_MODAL_ID, DeleteModal } from '../../components/dashboard/delete- import { useDeleteLink } from '../../components/dashboard/delete-modal/action'; import { QR_CODE_DIALOG_ID, QrCodeDialog } from '../../components/temporary-links/qr-code-dialog/qr-code-dialog'; import { addUtmParams } from '@reduced.to/utils'; +import { ISortBy, LinkAttribute, SortBy } from '../../components/dashboard/table/sort-by'; export default component$(() => { const toaster = useToaster(); @@ -24,6 +25,12 @@ export default component$(() => { const refetch = useSignal(0); const qrLink = useSignal(null); + // Client sorting + const clientSortBy = useStore({ + attribute: LinkAttribute.CreatedAt, + order: SortOrder.DESC, + }); + const isLoadingData = useSignal(true); const linksContainerRef = useSignal(); @@ -148,6 +155,19 @@ export default component$(() => { page.value = 1; // Reset page number when filter changes })} /> + { + clientSortBy.attribute = (e.target as HTMLSelectElement).value as LinkAttribute; + })} + toggleOrder={$(() => { + if (clientSortBy.order === SortOrder.ASC) { + clientSortBy.order = SortOrder.DESC; + } else { + clientSortBy.order = SortOrder.ASC; + } + })} + />
) : linksArray.length ? ( <> - {linksArray.map((link) => { - let url = link.url; + {linksArray + .sort((linkA, linkB) => { + let res = 0; + + switch (clientSortBy.attribute) { + case LinkAttribute.URL: + res = linkA.url.localeCompare(linkB.url); + break; + case LinkAttribute.Clicks: + res = linkA.clicks - linkB.clicks; + break; + case LinkAttribute.CreatedAt: + res = new Date(linkA.createdAt).getTime() - new Date(linkB.createdAt).getTime(); + break; + case LinkAttribute.ExpirationTime: + res = new Date(linkA.expirationTime ?? Date.now()).getTime() - new Date(linkB.expirationTime ?? Date.now()).getTime(); + break; + default: + break; + } + + return clientSortBy.order === SortOrder.ASC ? -res : res; + }) + .map((link) => { + let url = link.url; - if (link.utm) { - url = addUtmParams(url, link.utm); - } - return ( - { - qrLink.value = link.key; - (document.getElementById(QR_CODE_DIALOG_ID) as any).showModal(); - })} - onDelete={$((id: string) => { - idToDelete.value = id; - (document.getElementById('delete-modal') as any).showModal(); - })} - /> - ); - })} + if (link.utm) { + url = addUtmParams(url, link.utm); + } + return ( + { + qrLink.value = link.key; + (document.getElementById(QR_CODE_DIALOG_ID) as any).showModal(); + })} + onDelete={$((id: string) => { + idToDelete.value = id; + (document.getElementById('delete-modal') as any).showModal(); + })} + /> + ); + })} {isLoadingData.value && (