Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Client-Side Links Sorting #770

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions apps/frontend/src/components/dashboard/table/sort-by.tsx
Original file line number Diff line number Diff line change
@@ -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$<SortByProps>(({ sortBy, onChangeAttribute, toggleOrder }) => {
return (
<div class="flex items-center gap-2 mb-2">
<select
value={sortBy.attribute}
onChange$={(e) => onChangeAttribute(e)}
class="input input-bordered cursor-pointer h-12 pr-0 bg-base-100"
>
<option value={0} selected disabled hidden>
Sort By
</option>
<option value={LinkAttribute.URL}>URL</option>
<option value={LinkAttribute.Clicks}>Clicks</option>
<option value={LinkAttribute.CreatedAt}>Created At</option>
<option value={LinkAttribute.ExpirationTime}>Expiration Time</option>
</select>
<button onClick$={() => toggleOrder()}>{sortBy.order === SortOrder.ASC ? <LuArrowUp /> : <LuArrowDown />}</button>
</div>
);
});
95 changes: 69 additions & 26 deletions apps/frontend/src/routes/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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();
Expand All @@ -24,6 +25,12 @@ export default component$(() => {
const refetch = useSignal(0);
const qrLink = useSignal<string | null>(null);

// Client sorting
const clientSortBy = useStore<ISortBy>({
attribute: LinkAttribute.CreatedAt,
order: SortOrder.DESC,
});

const isLoadingData = useSignal(true);

const linksContainerRef = useSignal<HTMLElement>();
Expand Down Expand Up @@ -148,6 +155,19 @@ export default component$(() => {
page.value = 1; // Reset page number when filter changes
})}
/>
<SortBy
sortBy={clientSortBy}
onChangeAttribute={$((e: Event) => {
clientSortBy.attribute = (e.target as HTMLSelectElement).value as LinkAttribute;
})}
toggleOrder={$(() => {
if (clientSortBy.order === SortOrder.ASC) {
clientSortBy.order = SortOrder.DESC;
} else {
clientSortBy.order = SortOrder.ASC;
}
})}
/>
<div class="ml-auto pl-4">
<button class="btn btn-primary" onClick$={() => (document.getElementById(LINK_MODAL_ID) as any).showModal()}>
Create a new link
Expand All @@ -161,32 +181,55 @@ export default component$(() => {
</div>
) : 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 (
<LinkBlock
id={link.id}
key={link.key}
urlKey={link.key}
url={url}
clicks={link.clicks}
expirationTime={link.expirationTime}
createdAt={link.createdAt}
onShowQR={$(() => {
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 (
<LinkBlock
id={link.id}
key={link.key}
urlKey={link.key}
url={url}
clicks={link.clicks}
expirationTime={link.expirationTime}
createdAt={link.createdAt}
onShowQR={$(() => {
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 && (
<div class="flex items-center justify-center h-40">
<span class="loading loading-spinner loading-md"></span>
Expand Down