feat: add ProformaStatusSegmentedFilter component and integrate into list-proformas-page
This commit is contained in:
parent
1bf85a05ff
commit
fed29a71fd
@ -1,3 +1,4 @@
|
|||||||
export * from "./archive-proforma-dialog";
|
export * from "./archive-proforma-dialog";
|
||||||
export * from "./proforma-status-badge";
|
export * from "./proforma-status-badge";
|
||||||
export * from "./unarchive-proforma-dialog";
|
export * from "./unarchive-proforma-dialog";
|
||||||
|
export * from "./proforma-status-segmented-filter";
|
||||||
|
|||||||
@ -0,0 +1,77 @@
|
|||||||
|
import { ToggleGroup, ToggleGroupItem } from "@repo/shadcn-ui/components";
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
import type { ProformaListStatusFilter } from "../../types/proforma-list-filters";
|
||||||
|
|
||||||
|
export type ProformaStatusFilter = ProformaListStatusFilter;
|
||||||
|
|
||||||
|
export type ProformaStatusSegmentedFilterProps = {
|
||||||
|
value: ProformaStatusFilter;
|
||||||
|
onValueChange: (value: ProformaStatusFilter) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROFORMA_STATUS_FILTER_OPTIONS = [
|
||||||
|
{ value: "all", labelKey: "catalog.proformas.status.all.label" },
|
||||||
|
{ value: "draft", labelKey: "catalog.proformas.status.draft.label" },
|
||||||
|
{ value: "sent", labelKey: "catalog.proformas.status.sent.label" },
|
||||||
|
{ value: "approved", labelKey: "catalog.proformas.status.approved.label" },
|
||||||
|
{ value: "rejected", labelKey: "catalog.proformas.status.rejected.label" },
|
||||||
|
{ value: "issued", labelKey: "catalog.proformas.status.issued.label" },
|
||||||
|
] as const satisfies readonly {
|
||||||
|
value: ProformaStatusFilter;
|
||||||
|
labelKey: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const isProformaStatusFilter = (value: string): value is ProformaStatusFilter => {
|
||||||
|
return PROFORMA_STATUS_FILTER_OPTIONS.some((option) => option.value === value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProformaStatusSegmentedFilter = ({
|
||||||
|
value,
|
||||||
|
onValueChange,
|
||||||
|
disabled = false,
|
||||||
|
}: ProformaStatusSegmentedFilterProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleValueChange = (nextValue: string) => {
|
||||||
|
if (!(nextValue && isProformaStatusFilter(nextValue)) || nextValue === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChange(nextValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleGroup
|
||||||
|
aria-label={t("pages.proformas.list.filters.status.label")}
|
||||||
|
className="w-full flex-wrap justify-start"
|
||||||
|
disabled={disabled}
|
||||||
|
onValueChange={handleValueChange}
|
||||||
|
size="sm"
|
||||||
|
spacing={0}
|
||||||
|
type="single"
|
||||||
|
value={value}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
{PROFORMA_STATUS_FILTER_OPTIONS.map((option) => (
|
||||||
|
<ToggleGroupItem
|
||||||
|
className={cn(
|
||||||
|
"min-w-fit px-3 text-xs sm:text-sm",
|
||||||
|
"data-[state=on]:border-primary data-[state=on]:bg-primary data-[state=on]:text-primary-foreground data-[state=on]:shadow-none"
|
||||||
|
)}
|
||||||
|
key={option.value}
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
if (option.value === value) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={option.value}
|
||||||
|
>
|
||||||
|
{t(option.labelKey)}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
))}
|
||||||
|
</ToggleGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -8,18 +8,12 @@ import {
|
|||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
import {
|
import {
|
||||||
CheckCircle2Icon,
|
CheckCircle2Icon,
|
||||||
CircleDollarSignIcon,
|
CircleDollarSignIcon,
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
FilterIcon,
|
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { createSearchParams, useNavigate } from "react-router-dom";
|
import { createSearchParams, useNavigate } from "react-router-dom";
|
||||||
@ -32,10 +26,15 @@ import { prepareDeleteProformaTargets } from "../../../delete/utils";
|
|||||||
import { IssueProformaDialog } from "../../../issue-proforma";
|
import { IssueProformaDialog } from "../../../issue-proforma";
|
||||||
import { prepareIssueProformaTarget } from "../../../issue-proforma/utils";
|
import { prepareIssueProformaTarget } from "../../../issue-proforma/utils";
|
||||||
import type { ProformaListRow } from "../../../shared";
|
import type { ProformaListRow } from "../../../shared";
|
||||||
import type { ProformaListScope } from "../../types/proforma-list-filters";
|
|
||||||
import { useListProformasPageController } from "../../controllers";
|
import { useListProformasPageController } from "../../controllers";
|
||||||
|
import type { ProformaListScope } from "../../types/proforma-list-filters";
|
||||||
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
|
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
|
||||||
import { ArchiveProformaDialog, ProformaStatusBadge, UnarchiveProformaDialog } from "../components";
|
import {
|
||||||
|
ArchiveProformaDialog,
|
||||||
|
ProformaStatusBadge,
|
||||||
|
ProformaStatusSegmentedFilter,
|
||||||
|
UnarchiveProformaDialog,
|
||||||
|
} from "../components";
|
||||||
|
|
||||||
interface ScopedListProformasPageProps {
|
interface ScopedListProformasPageProps {
|
||||||
scope: ProformaListScope;
|
scope: ProformaListScope;
|
||||||
@ -136,23 +135,11 @@ const ScopedListProformasPage = ({ scope }: ScopedListProformasPageProps) => {
|
|||||||
value={listCtrl.search}
|
value={listCtrl.search}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<ProformaStatusSegmentedFilter
|
||||||
onValueChange={(value) => listCtrl.setStatusFilter(value ?? "all")}
|
disabled={listCtrl.isLoading}
|
||||||
|
onValueChange={listCtrl.setStatusFilter}
|
||||||
value={listCtrl.statusFilter}
|
value={listCtrl.statusFilter}
|
||||||
>
|
/>
|
||||||
<SelectTrigger className="w-full sm:w-48">
|
|
||||||
<FilterIcon aria-hidden className="mr-2 size-4" />
|
|
||||||
<SelectValue placeholder={t("pages.proformas.list.filters.status.label")} />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">{t("catalog.proformas.status.all.label")}</SelectItem>
|
|
||||||
<SelectItem value="draft">{t("catalog.proformas.status.draft.label")}</SelectItem>
|
|
||||||
<SelectItem value="sent">{t("catalog.proformas.status.sent.label")}</SelectItem>
|
|
||||||
<SelectItem value="approved">{t("catalog.proformas.status.approved.label")}</SelectItem>
|
|
||||||
<SelectItem value="rejected">{t("catalog.proformas.status.rejected.label")}</SelectItem>
|
|
||||||
<SelectItem value="issued">{t("catalog.proformas.status.issued.label")}</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ProformasGrid
|
<ProformasGrid
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
"typescript": "^6.0.2"
|
"typescript": "^6.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@base-ui/react": "^1.3.0",
|
"@base-ui/react": "^1.3.0",
|
||||||
"@fontsource-variable/geist": "^5.2.8",
|
"@fontsource-variable/geist": "^5.2.8",
|
||||||
"@fontsource-variable/geist-mono": "^5.2.7",
|
"@fontsource-variable/geist-mono": "^5.2.7",
|
||||||
|
|||||||
@ -1,87 +1,90 @@
|
|||||||
import * as React from "react"
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||||
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle"
|
import { toggleVariants } from "@repo/shadcn-ui/components/toggle";
|
||||||
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group"
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority";
|
||||||
|
import * as React from "react";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils"
|
|
||||||
import { toggleVariants } from "@repo/shadcn-ui/components/toggle"
|
|
||||||
|
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
VariantProps<typeof toggleVariants> & {
|
VariantProps<typeof toggleVariants> & {
|
||||||
spacing?: number
|
spacing?: number;
|
||||||
orientation?: "horizontal" | "vertical"
|
orientation?: "horizontal" | "vertical";
|
||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
size: "default",
|
size: "default",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
spacing: 0,
|
spacing: 0,
|
||||||
orientation: "horizontal",
|
orientation: "horizontal",
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const ToggleGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||||
|
VariantProps<typeof toggleVariants> & {
|
||||||
|
spacing?: number;
|
||||||
|
}
|
||||||
|
>(({ className, variant, size, spacing = 0, children, style, ...props }, ref) => {
|
||||||
|
const orientation = props.orientation ?? "horizontal";
|
||||||
|
|
||||||
function ToggleGroup({
|
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
size,
|
|
||||||
spacing = 0,
|
|
||||||
orientation = "horizontal",
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: ToggleGroupPrimitive.Props &
|
|
||||||
VariantProps<typeof toggleVariants> & {
|
|
||||||
spacing?: number
|
|
||||||
orientation?: "horizontal" | "vertical"
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<ToggleGroupPrimitive
|
<ToggleGroupPrimitive.Root
|
||||||
data-slot="toggle-group"
|
|
||||||
data-variant={variant}
|
|
||||||
data-size={size}
|
|
||||||
data-spacing={spacing}
|
|
||||||
data-orientation={orientation}
|
|
||||||
style={{ "--gap": spacing } as React.CSSProperties}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
|
"group/toggle-group flex w-fit rounded-md",
|
||||||
|
orientation === "vertical" ? "flex-col items-stretch" : "flex-row items-center",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
data-orientation={orientation}
|
||||||
|
data-size={size}
|
||||||
|
data-slot="toggle-group"
|
||||||
|
data-spacing={spacing}
|
||||||
|
data-variant={variant}
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
gap: spacing === 0 ? undefined : `${spacing * 0.25}rem`,
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ToggleGroupContext.Provider
|
<ToggleGroupContext.Provider value={{ variant, size, spacing, orientation }}>
|
||||||
value={{ variant, size, spacing, orientation }}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</ToggleGroupContext.Provider>
|
</ToggleGroupContext.Provider>
|
||||||
</ToggleGroupPrimitive>
|
</ToggleGroupPrimitive.Root>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||||
|
|
||||||
function ToggleGroupItem({
|
const ToggleGroupItem = React.forwardRef<
|
||||||
className,
|
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||||
children,
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||||
variant = "default",
|
VariantProps<typeof toggleVariants>
|
||||||
size = "default",
|
>(({ className, children, variant, size, ...props }, ref) => {
|
||||||
...props
|
const context = React.useContext(ToggleGroupContext);
|
||||||
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
||||||
const context = React.useContext(ToggleGroupContext)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TogglePrimitive
|
<ToggleGroupPrimitive.Item
|
||||||
data-slot="toggle-group-item"
|
|
||||||
data-variant={context.variant || variant}
|
|
||||||
data-size={context.size || size}
|
|
||||||
data-spacing={context.spacing}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md data-[state=on]:bg-muted group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
|
"shrink-0 rounded-md focus:z-10 focus-visible:z-10 data-[state=on]:bg-muted data-[state=on]:text-foreground",
|
||||||
|
"group-data-[spacing=0]/toggle-group:first:rounded-r-none group-data-[spacing=0]/toggle-group:last:rounded-l-none group-data-[spacing=0]/toggle-group:not-first:not-last:rounded-none",
|
||||||
|
"group-data-[spacing=0]/toggle-group:data-[orientation=vertical]:first:rounded-b-none group-data-[spacing=0]/toggle-group:data-[orientation=vertical]:last:rounded-t-none",
|
||||||
|
"group-data-[spacing=0]/toggle-group:data-[orientation=vertical]:not-first:not-last:rounded-none",
|
||||||
|
"group-data-[spacing=0]/toggle-group:not-first:-ml-px group-data-[spacing=0]/toggle-group:data-[orientation=vertical]:not-first:ml-0 group-data-[spacing=0]/toggle-group:data-[orientation=vertical]:not-first:-mt-px",
|
||||||
toggleVariants({
|
toggleVariants({
|
||||||
variant: context.variant || variant,
|
variant: context.variant ?? variant ?? "default",
|
||||||
size: context.size || size,
|
size: context.size ?? size ?? "default",
|
||||||
}),
|
}),
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
data-orientation={context.orientation}
|
||||||
|
data-size={context.size || size}
|
||||||
|
data-slot="toggle-group-item"
|
||||||
|
data-spacing={context.spacing}
|
||||||
|
data-variant={context.variant || variant}
|
||||||
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</TogglePrimitive>
|
</ToggleGroupPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||||
|
|
||||||
export { ToggleGroup, ToggleGroupItem }
|
export { ToggleGroup, ToggleGroupItem };
|
||||||
|
|||||||
@ -1383,6 +1383,9 @@ importers:
|
|||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.2.2
|
specifier: ^5.2.2
|
||||||
version: 5.4.0(react-hook-form@7.76.1(react@19.2.6))
|
version: 5.4.0(react-hook-form@7.76.1(react@19.2.6))
|
||||||
|
'@radix-ui/react-toggle-group':
|
||||||
|
specifier: ^1.1.11
|
||||||
|
version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||||
add:
|
add:
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6
|
version: 2.0.6
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user