98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
|
|
/**
|
||
|
|
* Entity Selector Component
|
||
|
|
* Checkbox grid for selecting entities to sync
|
||
|
|
*/
|
||
|
|
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import { EntityType } from '@/lib/types/sync';
|
||
|
|
import { getEntityDisplayName } from '@/lib/utils/sync-helpers';
|
||
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
||
|
|
import { Label } from '@/components/ui/label';
|
||
|
|
|
||
|
|
interface EntitySelectorProps {
|
||
|
|
selectedEntities: EntityType[];
|
||
|
|
onChange: (entities: EntityType[]) => void;
|
||
|
|
disabled?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
const ALL_ENTITIES: EntityType[] = [
|
||
|
|
EntityType.COMPANIES,
|
||
|
|
EntityType.RESOURCES,
|
||
|
|
EntityType.STATUSES,
|
||
|
|
EntityType.ISSUE_TYPES,
|
||
|
|
EntityType.SUB_ISSUE_TYPES,
|
||
|
|
EntityType.WORK_TYPES,
|
||
|
|
EntityType.CONTACTS,
|
||
|
|
EntityType.PROJECTS,
|
||
|
|
EntityType.TICKETS,
|
||
|
|
EntityType.TASKS,
|
||
|
|
EntityType.CONFIGURATION_ITEMS,
|
||
|
|
EntityType.CONTRACTS,
|
||
|
|
EntityType.BILLING_ITEMS,
|
||
|
|
EntityType.TIME_ENTRIES,
|
||
|
|
];
|
||
|
|
|
||
|
|
export default function EntitySelector({
|
||
|
|
selectedEntities,
|
||
|
|
onChange,
|
||
|
|
disabled = false,
|
||
|
|
}: EntitySelectorProps) {
|
||
|
|
const handleToggle = (entity: EntityType) => {
|
||
|
|
if (selectedEntities.includes(entity)) {
|
||
|
|
onChange(selectedEntities.filter(e => e !== entity));
|
||
|
|
} else {
|
||
|
|
onChange([...selectedEntities, entity]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSelectAll = () => {
|
||
|
|
if (selectedEntities.length === ALL_ENTITIES.length) {
|
||
|
|
onChange([]);
|
||
|
|
} else {
|
||
|
|
onChange([...ALL_ENTITIES]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const allSelected = selectedEntities.length === ALL_ENTITIES.length;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-4">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<Label className="text-base font-semibold">Select Entities</Label>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={handleSelectAll}
|
||
|
|
disabled={disabled}
|
||
|
|
className="text-sm text-primary hover:underline disabled:opacity-50"
|
||
|
|
>
|
||
|
|
{allSelected ? 'Deselect All' : 'Select All'}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 md:gap-4">
|
||
|
|
{ALL_ENTITIES.map((entity) => (
|
||
|
|
<div key={entity} className="flex items-center space-x-2">
|
||
|
|
<Checkbox
|
||
|
|
id={entity}
|
||
|
|
checked={selectedEntities.includes(entity)}
|
||
|
|
onCheckedChange={() => handleToggle(entity)}
|
||
|
|
disabled={disabled}
|
||
|
|
/>
|
||
|
|
<Label
|
||
|
|
htmlFor={entity}
|
||
|
|
className="text-sm font-normal cursor-pointer"
|
||
|
|
>
|
||
|
|
{getEntityDisplayName(entity)}
|
||
|
|
</Label>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="text-sm text-muted-foreground">
|
||
|
|
{selectedEntities.length} of {ALL_ENTITIES.length} entities selected
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|