Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
'use client'
|
||
|
||
import type { ReactNode } from 'react'
|
||
|
||
import { Toolbar } from '@heroui/react'
|
||
|
||
import type { Selection } from '@/types/heroui'
|
||
import Button from '@/components/formElements/Button'
|
||
import AngleDownIcon from '@/components/icons/AngleDownIcon'
|
||
|
||
interface Props {
|
||
activeFilterCount: number
|
||
backBtnUrl?: string
|
||
bulkActions?: ReactNode
|
||
dynamicButtonText: string
|
||
dynamicTopSection?: ReactNode
|
||
hasDynamicButton?: boolean
|
||
hasFilters: boolean
|
||
selectedKeys: Selection
|
||
selectionMode: 'single' | 'multiple' | 'none'
|
||
onBack: (url: string) => void
|
||
onDynamicButtonClick?: () => void
|
||
onOpenFilters: () => void
|
||
onClearSelection: () => void
|
||
}
|
||
|
||
export default function PaginatedListToolbar({
|
||
activeFilterCount,
|
||
backBtnUrl,
|
||
bulkActions,
|
||
dynamicButtonText,
|
||
dynamicTopSection,
|
||
hasDynamicButton,
|
||
hasFilters,
|
||
selectedKeys,
|
||
selectionMode,
|
||
onBack,
|
||
onDynamicButtonClick,
|
||
onOpenFilters,
|
||
onClearSelection,
|
||
}: Props) {
|
||
const hasSelection = selectionMode !== 'none' && (selectedKeys === 'all' || selectedKeys.size > 0)
|
||
const hasDesktopToolbarContent = Boolean(backBtnUrl || dynamicTopSection || hasDynamicButton || hasSelection)
|
||
const hasToolbarContent = hasDesktopToolbarContent || hasFilters || hasSelection
|
||
|
||
if (!hasToolbarContent) return null
|
||
|
||
return (
|
||
<Toolbar
|
||
aria-label="ابزارهای فهرست"
|
||
className={`flex min-h-16 flex-wrap items-center justify-between gap-3 border-b border-secondary-40 bg-white px-4 py-3 lg:flex-row ${
|
||
hasDesktopToolbarContent ? '' : 'md:hidden'
|
||
}`}
|
||
>
|
||
<div className="flex w-full flex-1 items-center gap-3 md:w-auto">
|
||
{backBtnUrl ? (
|
||
<div className="hidden shrink-0 md:block">
|
||
<Button
|
||
iconOnly
|
||
aria-label="بازگشت"
|
||
className="z-10 !size-10 !rounded-2xl bg-secondary-30/30"
|
||
variant="solid"
|
||
onClick={() => {
|
||
onBack(backBtnUrl)
|
||
}}
|
||
>
|
||
<AngleDownIcon />
|
||
</Button>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
<div className="flex w-full flex-col items-stretch gap-2 sm:w-auto sm:flex-row sm:items-center">
|
||
{hasSelection ? (
|
||
<div
|
||
aria-live="polite"
|
||
className="flex min-h-10 items-center gap-2 rounded-xl border border-accent-soft bg-accent-soft px-3 text-xs font-semibold text-accent-soft-foreground"
|
||
role="status"
|
||
>
|
||
<span>
|
||
{selectedKeys === 'all' ? 'همه سطرها انتخاب شدهاند' : `${selectedKeys.size.toLocaleString('fa-IR')} سطر انتخاب شده`}
|
||
</span>
|
||
{bulkActions}
|
||
<Button
|
||
color="default"
|
||
size="sm"
|
||
variant="light"
|
||
onClick={onClearSelection}
|
||
>
|
||
لغو انتخاب
|
||
</Button>
|
||
</div>
|
||
) : null}
|
||
{hasFilters ? (
|
||
<Button
|
||
className="md:hidden"
|
||
color="default"
|
||
size="sm"
|
||
variant="flat"
|
||
onClick={onOpenFilters}
|
||
>
|
||
فیلترها{activeFilterCount > 0 ? ` (${activeFilterCount.toLocaleString('fa-IR')})` : ''}
|
||
</Button>
|
||
) : null}
|
||
{dynamicTopSection}
|
||
{hasDynamicButton && <Button onClick={onDynamicButtonClick}>{dynamicButtonText}</Button>}
|
||
</div>
|
||
</Toolbar>
|
||
)
|
||
}
|