Files
ai-agent/web/components/dashboard/crud/dashboard-crud-presets.tsx
T

131 lines
3.7 KiB
TypeScript

"use client"
import type { ComponentProps, ReactNode } from "react"
import { toast } from "sonner"
import { Badge } from "@/components/ui/badge"
import { Switch } from "@/components/ui/switch"
import type {
DashboardCrudColumn,
DashboardCrudRowAction,
} from "./dashboard-crud-page"
type BadgeVariant = ComponentProps<typeof Badge>["variant"]
export type DashboardCrudStatusColumnOptions<TItem, TStatus> = {
key?: string
label: ReactNode
className?: string
getStatus: (item: TItem) => TStatus
getLabel: (status: TStatus, item: TItem) => ReactNode
getBadgeVariant?: (status: TStatus, item: TItem) => BadgeVariant
isEnabled?: (status: TStatus, item: TItem) => boolean
toggle?: {
disabled?: (item: TItem) => boolean
getNextStatus: (item: TItem) => TStatus
updateStatus: (item: TItem, nextStatus: TStatus) => Promise<unknown>
successMessage: (item: TItem, nextStatus: TStatus) => string
errorMessage: string
ariaLabel: (item: TItem) => string
}
}
export type DashboardCrudStatusToggleActionOptions<TItem, TStatus> = {
key?: string
icon?: ReactNode | ((item: TItem) => ReactNode)
label: (item: TItem, nextStatus: TStatus) => ReactNode
visible?: (item: TItem) => boolean
disabled?: (item: TItem) => boolean
getNextStatus: (item: TItem) => TStatus
updateStatus: (item: TItem, nextStatus: TStatus) => Promise<unknown>
successMessage: (item: TItem, nextStatus: TStatus) => string
errorMessage: string
}
export function createDashboardStatusColumn<TItem, TStatus>({
key = "status",
label,
className,
getStatus,
getLabel,
getBadgeVariant,
isEnabled,
toggle,
}: DashboardCrudStatusColumnOptions<TItem, TStatus>): DashboardCrudColumn<TItem> {
return {
key,
label,
className,
render: (item, { itemId, actionLoading, reload, setActionLoadingId }) => {
const status = getStatus(item)
const badge = (
<Badge variant={getBadgeVariant?.(status, item) ?? "outline"}>
{getLabel(status, item)}
</Badge>
)
if (!toggle) {
return badge
}
return (
<div className="flex items-center gap-3">
<Switch
checked={isEnabled ? isEnabled(status, item) : Boolean(status)}
disabled={actionLoading || toggle.disabled?.(item)}
onCheckedChange={() => {
void (async () => {
setActionLoadingId(itemId)
try {
const nextStatus = toggle.getNextStatus(item)
await toggle.updateStatus(item, nextStatus)
toast.success(toggle.successMessage(item, nextStatus))
await reload()
} catch (error) {
toast.error(
error instanceof Error ? error.message : toggle.errorMessage
)
} finally {
setActionLoadingId(null)
}
})()
}}
aria-label={toggle.ariaLabel(item)}
/>
{badge}
</div>
)
},
}
}
export function createDashboardStatusToggleAction<TItem, TStatus>({
key = "toggle-status",
icon,
label,
visible,
disabled,
getNextStatus,
updateStatus,
successMessage,
errorMessage,
}: DashboardCrudStatusToggleActionOptions<TItem, TStatus>): DashboardCrudRowAction<TItem> {
return {
key,
icon,
visible,
disabled,
label: (item) => label(item, getNextStatus(item)),
run: async ({ item, reload }) => {
try {
const nextStatus = getNextStatus(item)
await updateStatus(item, nextStatus)
toast.success(successMessage(item, nextStatus))
await reload()
} catch (error) {
toast.error(error instanceof Error ? error.message : errorMessage)
}
},
}
}