Re_Figma_Code/src/components/common/HsnSacSelector.tsx

131 lines
4.6 KiB
TypeScript

import * as React from "react";
import { Check, ChevronsUpDown, Search } from "lucide-react";
import { cn } from "@/components/ui/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { getAllHsnSacCodes, HsnSacCode } from "@/services/hsnSacCodeApi";
interface HsnSacSelectorProps {
value: string;
onChange: (value: string) => void;
type: "HSN" | "SAC";
placeholder?: string;
disabled?: boolean;
className?: string;
}
export function HsnSacSelector({
value,
onChange,
type,
placeholder,
disabled,
className,
}: HsnSacSelectorProps) {
const [open, setOpen] = React.useState(false);
const [allCodes, setAllCodes] = React.useState<HsnSacCode[]>([]);
React.useEffect(() => {
const fetchCodes = async () => {
try {
const response = await getAllHsnSacCodes(true, 1, 1000);
setAllCodes(response.codes);
} catch (error) {
console.error("Failed to fetch HSN/SAC codes for selector:", error);
}
};
if (open && allCodes.length === 0) {
fetchCodes();
}
}, [open, allCodes.length]);
const filteredCodes = React.useMemo(() => {
return allCodes.filter((c) => c.type === type);
}, [allCodes, type]);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
disabled={disabled}
className={cn("w-full justify-between bg-white font-normal h-9 px-3 text-xs sm:text-sm border-slate-200 hover:bg-slate-50", className)}
>
<span className="font-medium text-slate-900">
{value
? allCodes.find((code) => code.code === value)?.code || value
: placeholder || `Select ${type}...`}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[320px] p-0 shadow-2xl border-slate-200" align="start">
<Command className="rounded-lg border-0">
<div className="bg-slate-50/80 p-2.5 flex items-center gap-2">
<Search className="w-4 h-4 text-slate-400" />
<CommandInput
placeholder={`Search ${type} code...`}
wrapperClassName="border-none p-0 h-auto flex-1"
className="h-8 border-none bg-transparent shadow-none ring-0 focus-visible:ring-0 placeholder:text-slate-400 text-sm"
/>
</div>
<CommandList className="max-h-[350px] scrollbar-thin scrollbar-thumb-slate-200">
<CommandEmpty className="py-8 text-center">
<div className="flex flex-col items-center gap-2">
<Search className="h-8 w-8 text-slate-200" />
<p className="text-sm font-medium text-slate-500">No {type} code found</p>
<p className="text-xs text-slate-400">Try a different search term</p>
</div>
</CommandEmpty>
<CommandGroup className="p-1.5">
{filteredCodes.map((code) => (
<CommandItem
key={code.id}
value={code.code}
onSelect={(currentValue) => {
onChange(currentValue);
setOpen(false);
}}
className="flex flex-col items-start gap-1 p-2.5 rounded-md aria-selected:bg-slate-100 transition-colors cursor-pointer mb-1 last:mb-0"
>
<div className="flex items-center w-full justify-between">
<span className="font-bold text-sm text-slate-900 leading-none">{code.code}</span>
<div className="flex items-center gap-2">
<Check
className={cn(
"h-4 w-4 text-re-green transition-all",
value === code.code ? "opacity-100 scale-100" : "opacity-0 scale-75"
)}
/>
</div>
</div>
{code.description && (
<span className="text-[11px] text-slate-500 line-clamp-2 leading-tight pr-4">
{code.description}
</span>
)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}