feat: redesign routed ZarinPal merchant dashboard
This commit is contained in:
207
src/lib/components/AppShell.svelte
Normal file
207
src/lib/components/AppShell.svelte
Normal file
@@ -0,0 +1,207 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import ActivityIcon from '@lucide/svelte/icons/activity';
|
||||
import ArrowUpDownIcon from '@lucide/svelte/icons/arrow-up-down';
|
||||
import BookOpenIcon from '@lucide/svelte/icons/book-open';
|
||||
import ChartNoAxesCombinedIcon from '@lucide/svelte/icons/chart-no-axes-combined';
|
||||
import HandCoinsIcon from '@lucide/svelte/icons/hand-coins';
|
||||
import LogOutIcon from '@lucide/svelte/icons/log-out';
|
||||
import MenuIcon from '@lucide/svelte/icons/menu';
|
||||
import MoonIcon from '@lucide/svelte/icons/moon';
|
||||
import PanelsTopLeftIcon from '@lucide/svelte/icons/panels-top-left';
|
||||
import SunIcon from '@lucide/svelte/icons/sun';
|
||||
import UsersRoundIcon from '@lucide/svelte/icons/users-round';
|
||||
import WalletCardsIcon from '@lucide/svelte/icons/wallet-cards';
|
||||
|
||||
import BrandLogo from '$lib/components/BrandLogo.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar';
|
||||
import { useDashboardContext } from '$lib/dashboard-context.svelte';
|
||||
import { setDashboardQuery } from '$lib/dashboard-filters';
|
||||
import { formatValue } from '$lib/format';
|
||||
|
||||
let { children } = $props();
|
||||
const dashboard = useDashboardContext();
|
||||
let dark = $state(document.documentElement.dataset.theme === 'dark');
|
||||
|
||||
const items = [
|
||||
{ href: '/dashboard', label: 'پیشخوان', icon: PanelsTopLeftIcon },
|
||||
{ href: '/transactions', label: 'تراکنشها', icon: ArrowUpDownIcon },
|
||||
{ href: '/payments', label: 'عملکرد پرداخت', icon: ActivityIcon },
|
||||
{ href: '/fees', label: 'کارمزدها', icon: HandCoinsIcon },
|
||||
{ href: '/peers', label: 'همگروهها', icon: UsersRoundIcon },
|
||||
];
|
||||
|
||||
function navUrl(path: string) {
|
||||
const url = new URL(page.url);
|
||||
url.pathname = path;
|
||||
for (const key of [...url.searchParams.keys()]) {
|
||||
if (!['range', 'terminal'].includes(key)) url.searchParams.delete(key);
|
||||
}
|
||||
return `${url.pathname}${url.search}`;
|
||||
}
|
||||
|
||||
function isActive(path: string) {
|
||||
return (
|
||||
page.url.pathname === path ||
|
||||
(path === '/transactions' && page.url.pathname.startsWith('/transactions/'))
|
||||
);
|
||||
}
|
||||
|
||||
function terminalChanged(event: Event) {
|
||||
const next = setDashboardQuery(page.url, {
|
||||
terminal: (event.currentTarget as HTMLSelectElement).value,
|
||||
});
|
||||
void goto(`${next.pathname}${next.search}`, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
dark = !dark;
|
||||
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
localStorage.setItem('zpa-theme', dark ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await dashboard.logout();
|
||||
await goto('/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<Sidebar.Provider class="dashboard-shell" style="--sidebar-width: 16rem; --sidebar-width-icon: 4.5rem;">
|
||||
<Sidebar.Root side="right" collapsible="icon" class="top-14 h-[calc(100svh-3.5rem)] border-l">
|
||||
<Sidebar.Header class="border-sidebar-border gap-2 border-b p-2.5">
|
||||
<div class="terminal-card">
|
||||
<WalletCardsIcon class="size-5 shrink-0" />
|
||||
<div class="min-w-0 group-data-[collapsible=icon]:hidden">
|
||||
<strong class="block truncate text-sm">{dashboard.session?.merchant.displayName}</strong>
|
||||
<span class="block truncate text-[11px] text-white/75">حساب تحلیل پذیرنده</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-data-[collapsible=icon]:hidden">
|
||||
<label class="text-muted-foreground mb-1 block text-[11px]" for="sidebar-terminal-select"
|
||||
>درگاه فعال گزارش</label
|
||||
>
|
||||
<select
|
||||
id="sidebar-terminal-select"
|
||||
value={page.url.searchParams.get('terminal') ?? 'all'}
|
||||
onchange={terminalChanged}
|
||||
class="border-input bg-background focus:ring-ring/30 h-9 w-full rounded-md border px-2 text-xs outline-none focus:ring-2"
|
||||
>
|
||||
<option value="all">همه درگاهها</option>
|
||||
{#each dashboard.terminals as terminal}
|
||||
<option value={terminal.terminalId}
|
||||
>{terminal.displayName} · {formatValue(terminal.sessionCount)}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</Sidebar.Header>
|
||||
<Sidebar.Content class="px-2 py-3">
|
||||
<Sidebar.Group>
|
||||
<Sidebar.GroupContent>
|
||||
<Sidebar.Menu class="gap-1">
|
||||
{#each items as item}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton
|
||||
isActive={isActive(item.href)}
|
||||
tooltipContent={item.label}
|
||||
class="data-[active=true]:text-primary h-10 justify-start px-3 text-[13px]"
|
||||
onclick={() => goto(navUrl(item.href))}
|
||||
>
|
||||
<item.icon class="size-[19px]" strokeWidth={1.8} />
|
||||
<span>{item.label}</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/each}
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.GroupContent>
|
||||
</Sidebar.Group>
|
||||
<Sidebar.Separator class="my-2" />
|
||||
<Sidebar.Group>
|
||||
<Sidebar.GroupContent>
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton
|
||||
isActive={isActive('/methodology')}
|
||||
tooltipContent="روش محاسبه"
|
||||
class="data-[active=true]:text-primary h-10 justify-start px-3 text-[13px]"
|
||||
onclick={() => goto(navUrl('/methodology'))}
|
||||
>
|
||||
<BookOpenIcon class="size-[19px]" strokeWidth={1.8} />
|
||||
<span>روش محاسبه</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.GroupContent>
|
||||
</Sidebar.Group>
|
||||
</Sidebar.Content>
|
||||
<Sidebar.Footer class="border-sidebar-border border-t p-2">
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton tooltipContent="خروج" class="h-10 px-3" onclick={logout}>
|
||||
<LogOutIcon class="size-[18px]" />
|
||||
<span>خروج از حساب</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.Footer>
|
||||
<Sidebar.Rail />
|
||||
</Sidebar.Root>
|
||||
|
||||
<div
|
||||
class="bg-card fixed inset-x-0 top-0 z-30 flex h-14 items-center border-b px-4 shadow-[0_1px_2px_rgba(0,0,0,.03)]"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Sidebar.Trigger class="size-9" aria-label="باز و بسته کردن منو"><MenuIcon /></Sidebar.Trigger>
|
||||
<BrandLogo />
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<label class="sr-only" for="mobile-terminal-select">درگاه فعال گزارش</label>
|
||||
<select
|
||||
id="mobile-terminal-select"
|
||||
value={page.url.searchParams.get('terminal') ?? 'all'}
|
||||
onchange={terminalChanged}
|
||||
class="border-input bg-background h-8 max-w-36 rounded-md border px-2 text-xs md:hidden"
|
||||
>
|
||||
<option value="all">همه درگاهها</option>
|
||||
{#each dashboard.terminals as terminal}
|
||||
<option value={terminal.terminalId}>{terminal.displayName}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button variant="ghost" size="icon" onclick={toggleTheme} aria-label="تغییر پوسته">
|
||||
{#if dark}<SunIcon />{:else}<MoonIcon />{/if}
|
||||
</Button>
|
||||
<span class="bg-border mx-1 h-6 w-px"></span>
|
||||
<div class="grid size-8 place-items-center rounded-full bg-zinc-900 text-xs font-bold text-white">
|
||||
{dashboard.session?.user.username.slice(0, 1).toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Sidebar.Inset class="bg-background mt-14 min-w-0">
|
||||
<main class="mx-auto w-full max-w-[1220px] px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
|
||||
{@render children()}
|
||||
</main>
|
||||
</Sidebar.Inset>
|
||||
</Sidebar.Provider>
|
||||
|
||||
<style>
|
||||
:global(.dashboard-shell) {
|
||||
direction: rtl;
|
||||
}
|
||||
.terminal-card {
|
||||
display: flex;
|
||||
min-height: 52px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 7px;
|
||||
background: var(--primary);
|
||||
padding: 10px;
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user