|
1 | | -import { useMemo, useState } from 'react'; |
| 1 | +import { useEffect, useMemo, useState } from 'react'; |
2 | 2 | import { |
| 3 | + CheckIcon, |
3 | 4 | FolderIcon, |
4 | 5 | FolderOpenIcon, |
5 | 6 | FolderPlusIcon, |
| 7 | + PencilIcon, |
6 | 8 | SearchIcon, |
7 | 9 | TrashIcon, |
8 | 10 | XIcon, |
9 | 11 | } from 'lucide-react'; |
| 12 | +import { |
| 13 | + InputGroup, |
| 14 | + InputGroupAddon, |
| 15 | + InputGroupButton, |
| 16 | + InputGroupInput, |
| 17 | +} from '@/components/ui/input-group'; |
10 | 18 | import { TooltipTrigger } from '@radix-ui/react-tooltip'; |
11 | 19 | import type { LaboratoryCollection, LaboratoryCollectionOperation } from '../../lib/collections'; |
12 | 20 | import { cn } from '../../lib/utils'; |
@@ -44,74 +52,163 @@ export const CollectionItem = (props: { collection: LaboratoryCollection }) => { |
44 | 52 | addOperation, |
45 | 53 | setActiveOperation, |
46 | 54 | deleteCollection, |
| 55 | + updateCollection, |
47 | 56 | deleteOperationFromCollection, |
48 | 57 | addTab, |
49 | 58 | setActiveTab, |
50 | 59 | checkPermissions, |
51 | 60 | } = useLaboratory(); |
52 | 61 |
|
53 | 62 | const [isOpen, setIsOpen] = useState(false); |
| 63 | + const [isEditing, setIsEditing] = useState(false); |
| 64 | + const [editedName, setEditedName] = useState(props.collection.name); |
| 65 | + |
| 66 | + const hasActiveOperation = useMemo(() => { |
| 67 | + return props.collection.operations.some(operation => operation.id === activeOperation?.id); |
| 68 | + }, [props.collection.operations, activeOperation]); |
| 69 | + |
| 70 | + useEffect(() => { |
| 71 | + if (hasActiveOperation) { |
| 72 | + setIsOpen(true); |
| 73 | + } |
| 74 | + }, [hasActiveOperation]); |
54 | 75 |
|
55 | 76 | return ( |
56 | 77 | <Collapsible open={isOpen} onOpenChange={setIsOpen}> |
57 | 78 | <CollapsibleTrigger asChild> |
58 | | - <Button |
59 | | - variant="ghost" |
60 | | - className="bg-background group sticky top-0 w-full justify-start px-2" |
61 | | - size="sm" |
62 | | - > |
63 | | - {isOpen ? ( |
64 | | - <FolderOpenIcon className="text-muted-foreground size-4" /> |
65 | | - ) : ( |
66 | | - <FolderIcon className="text-muted-foreground size-4" /> |
67 | | - )} |
68 | | - {props.collection.name} |
69 | | - {checkPermissions?.('collections:delete') && ( |
70 | | - <Tooltip> |
71 | | - <TooltipTrigger asChild> |
72 | | - <AlertDialog> |
73 | | - <AlertDialogTrigger asChild> |
| 79 | + {isEditing ? ( |
| 80 | + <InputGroup className="!bg-accent/50 h-8 border-none"> |
| 81 | + <InputGroupAddon className="pl-2.5"> |
| 82 | + {isOpen ? ( |
| 83 | + <FolderOpenIcon className="text-muted-foreground size-4" /> |
| 84 | + ) : ( |
| 85 | + <FolderIcon className="text-muted-foreground size-4" /> |
| 86 | + )} |
| 87 | + </InputGroupAddon> |
| 88 | + <InputGroupInput |
| 89 | + autoFocus |
| 90 | + defaultValue={editedName} |
| 91 | + className="!pl-1.5 font-medium" |
| 92 | + onChange={e => setEditedName(e.target.value)} |
| 93 | + onKeyDown={e => { |
| 94 | + if (e.key === 'Enter') { |
| 95 | + updateCollection(props.collection.id, { |
| 96 | + name: editedName, |
| 97 | + }); |
| 98 | + setIsEditing(false); |
| 99 | + } |
| 100 | + if (e.key === 'Escape') { |
| 101 | + setEditedName(props.collection.name); |
| 102 | + setIsEditing(false); |
| 103 | + } |
| 104 | + }} |
| 105 | + /> |
| 106 | + <InputGroupAddon align="inline-end"> |
| 107 | + <InputGroupButton |
| 108 | + className="p-1!" |
| 109 | + onClick={e => { |
| 110 | + e.stopPropagation(); |
| 111 | + |
| 112 | + updateCollection(props.collection.id, { |
| 113 | + name: editedName, |
| 114 | + }); |
| 115 | + |
| 116 | + setIsEditing(false); |
| 117 | + }} |
| 118 | + > |
| 119 | + <CheckIcon /> |
| 120 | + </InputGroupButton> |
| 121 | + <InputGroupButton |
| 122 | + className="p-1!" |
| 123 | + onClick={e => { |
| 124 | + e.stopPropagation(); |
| 125 | + |
| 126 | + setIsEditing(false); |
| 127 | + setEditedName(props.collection.name); |
| 128 | + }} |
| 129 | + > |
| 130 | + <XIcon /> |
| 131 | + </InputGroupButton> |
| 132 | + </InputGroupAddon> |
| 133 | + </InputGroup> |
| 134 | + ) : ( |
| 135 | + <Button |
| 136 | + variant="ghost" |
| 137 | + className="bg-background !hover:bg-accent/50 group sticky top-0 w-full justify-start px-2" |
| 138 | + size="sm" |
| 139 | + > |
| 140 | + {isOpen ? ( |
| 141 | + <FolderOpenIcon className="text-muted-foreground size-4" /> |
| 142 | + ) : ( |
| 143 | + <FolderIcon className="text-muted-foreground size-4" /> |
| 144 | + )} |
| 145 | + {props.collection.name} |
| 146 | + <div className="ml-auto flex items-center gap-2"> |
| 147 | + {checkPermissions?.('collections:update') && ( |
| 148 | + <Tooltip> |
| 149 | + <TooltipTrigger> |
74 | 150 | <Button |
75 | 151 | variant="link" |
76 | | - className="text-muted-foreground hover:text-destructive p-1! pr-0! ml-auto opacity-0 transition-opacity group-hover:opacity-100" |
| 152 | + className="text-muted-foreground p-1! pr-0! opacity-0 transition-opacity group-hover:opacity-100" |
77 | 153 | onClick={e => { |
78 | 154 | e.stopPropagation(); |
| 155 | + setIsEditing(true); |
79 | 156 | }} |
80 | 157 | > |
81 | | - <TrashIcon /> |
| 158 | + <PencilIcon /> |
82 | 159 | </Button> |
83 | | - </AlertDialogTrigger> |
84 | | - <AlertDialogContent> |
85 | | - <AlertDialogHeader> |
86 | | - <AlertDialogTitle> |
87 | | - Are you sure you want to delete collection? |
88 | | - </AlertDialogTitle> |
89 | | - <AlertDialogDescription> |
90 | | - {props.collection.name} will be permanently deleted. All operations in this |
91 | | - collection will be deleted as well. |
92 | | - </AlertDialogDescription> |
93 | | - </AlertDialogHeader> |
94 | | - <AlertDialogFooter> |
95 | | - <AlertDialogCancel>Cancel</AlertDialogCancel> |
96 | | - <AlertDialogAction asChild> |
| 160 | + </TooltipTrigger> |
| 161 | + <TooltipContent>Edit collection</TooltipContent> |
| 162 | + </Tooltip> |
| 163 | + )} |
| 164 | + {checkPermissions?.('collections:delete') && ( |
| 165 | + <Tooltip> |
| 166 | + <TooltipTrigger> |
| 167 | + <AlertDialog> |
| 168 | + <AlertDialogTrigger asChild> |
97 | 169 | <Button |
98 | | - variant="destructive" |
| 170 | + variant="link" |
| 171 | + className="text-muted-foreground hover:text-destructive p-1! pr-0! opacity-0 transition-opacity group-hover:opacity-100" |
99 | 172 | onClick={e => { |
100 | 173 | e.stopPropagation(); |
101 | | - deleteCollection(props.collection.id); |
102 | 174 | }} |
103 | 175 | > |
104 | | - Delete |
| 176 | + <TrashIcon /> |
105 | 177 | </Button> |
106 | | - </AlertDialogAction> |
107 | | - </AlertDialogFooter> |
108 | | - </AlertDialogContent> |
109 | | - </AlertDialog> |
110 | | - </TooltipTrigger> |
111 | | - <TooltipContent>Delete collection</TooltipContent> |
112 | | - </Tooltip> |
113 | | - )} |
114 | | - </Button> |
| 178 | + </AlertDialogTrigger> |
| 179 | + <AlertDialogContent> |
| 180 | + <AlertDialogHeader> |
| 181 | + <AlertDialogTitle> |
| 182 | + Are you sure you want to delete collection? |
| 183 | + </AlertDialogTitle> |
| 184 | + <AlertDialogDescription> |
| 185 | + {props.collection.name} will be permanently deleted. All operations in |
| 186 | + this collection will be deleted as well. |
| 187 | + </AlertDialogDescription> |
| 188 | + </AlertDialogHeader> |
| 189 | + <AlertDialogFooter> |
| 190 | + <AlertDialogCancel>Cancel</AlertDialogCancel> |
| 191 | + <AlertDialogAction asChild> |
| 192 | + <Button |
| 193 | + variant="destructive" |
| 194 | + onClick={e => { |
| 195 | + e.stopPropagation(); |
| 196 | + deleteCollection(props.collection.id); |
| 197 | + }} |
| 198 | + > |
| 199 | + Delete |
| 200 | + </Button> |
| 201 | + </AlertDialogAction> |
| 202 | + </AlertDialogFooter> |
| 203 | + </AlertDialogContent> |
| 204 | + </AlertDialog> |
| 205 | + </TooltipTrigger> |
| 206 | + <TooltipContent>Delete collection</TooltipContent> |
| 207 | + </Tooltip> |
| 208 | + )} |
| 209 | + </div> |
| 210 | + </Button> |
| 211 | + )} |
115 | 212 | </CollapsibleTrigger> |
116 | 213 | <CollapsibleContent className={cn('border-border ml-4 flex flex-col gap-1 border-l pl-2')}> |
117 | 214 | {isOpen && |
|
0 commit comments