@mdguggenbichler/slugbase-core 0.0.31 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frontend/src/components/FilterChips.tsx +5 -3
- package/frontend/src/components/StatCard.tsx +82 -5
- package/frontend/src/components/bookmarks/BookmarkCard.tsx +317 -210
- package/frontend/src/components/bookmarks/BookmarkTableView.tsx +47 -23
- package/frontend/src/components/collections/CollectionToolbar.tsx +294 -0
- package/frontend/src/components/collections/README.md +44 -0
- package/frontend/src/components/collections/index.ts +2 -0
- package/frontend/src/components/dashboard/DashboardHeader.tsx +16 -0
- package/frontend/src/components/dashboard/MostUsedTagsSection.tsx +49 -0
- package/frontend/src/components/dashboard/PinnedSection.tsx +110 -0
- package/frontend/src/components/dashboard/QuickAccessSection.tsx +120 -0
- package/frontend/src/components/dashboard/README.md +35 -0
- package/frontend/src/components/dashboard/StatsCardsRow.tsx +78 -0
- package/frontend/src/components/dashboard/index.ts +17 -0
- package/frontend/src/locales/de.json +2 -0
- package/frontend/src/locales/en.json +1 -0
- package/frontend/src/locales/es.json +2 -0
- package/frontend/src/locales/fr.json +2 -0
- package/frontend/src/locales/it.json +2 -0
- package/frontend/src/locales/ja.json +2 -0
- package/frontend/src/locales/nl.json +2 -0
- package/frontend/src/locales/pl.json +2 -0
- package/frontend/src/locales/pt.json +2 -0
- package/frontend/src/locales/ru.json +2 -0
- package/frontend/src/locales/zh.json +2 -0
- package/frontend/src/pages/Bookmarks.tsx +97 -214
- package/frontend/src/pages/Dashboard.tsx +99 -216
- package/frontend/src/pages/Folders.tsx +181 -251
- package/frontend/src/pages/Tags.tsx +87 -145
- package/package.json +1 -1
|
@@ -4,19 +4,17 @@ import { useTranslation } from 'react-i18next';
|
|
|
4
4
|
import api from '../api/client';
|
|
5
5
|
import ConfirmDialog from '../components/ui/ConfirmDialog';
|
|
6
6
|
import { useConfirmDialog } from '../hooks/useConfirmDialog';
|
|
7
|
-
import { Plus, Edit, Trash2, Share2,
|
|
7
|
+
import { Plus, Edit, Trash2, Share2, Folder, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
8
8
|
import FolderModal from '../components/modals/FolderModal';
|
|
9
9
|
import ShareResourceDialog from '../components/sharing/ShareResourceDialog';
|
|
10
10
|
import Button from '../components/ui/Button';
|
|
11
|
-
import Select from '../components/ui/Select';
|
|
12
11
|
import Tooltip from '../components/ui/Tooltip';
|
|
13
12
|
import FolderIcon from '../components/FolderIcon';
|
|
14
|
-
import {
|
|
13
|
+
import { CollectionToolbar } from '../components/collections';
|
|
15
14
|
import { EmptyState } from '../components/EmptyState';
|
|
16
15
|
import { PageLoadingSkeleton } from '../components/ui/PageLoadingSkeleton';
|
|
17
16
|
import { useAppConfig } from '../contexts/AppConfigContext';
|
|
18
|
-
import {
|
|
19
|
-
import { FilterChips } from '../components/FilterChips';
|
|
17
|
+
import { Card } from '../components/ui/card';
|
|
20
18
|
|
|
21
19
|
interface Folder {
|
|
22
20
|
id: string;
|
|
@@ -49,9 +47,6 @@ export default function Folders() {
|
|
|
49
47
|
const saved = localStorage.getItem('folders-view-mode');
|
|
50
48
|
return (saved === 'list' || saved === 'card') ? saved : 'card';
|
|
51
49
|
});
|
|
52
|
-
const [compactMode, setCompactMode] = useState(() => {
|
|
53
|
-
return localStorage.getItem('folders-compact-mode') === 'true';
|
|
54
|
-
});
|
|
55
50
|
|
|
56
51
|
const scopeParam = searchParams.get('scope');
|
|
57
52
|
const scope = (scopeParam === 'mine' || scopeParam === 'shared_with_me' || scopeParam === 'shared_by_me')
|
|
@@ -71,10 +66,6 @@ export default function Folders() {
|
|
|
71
66
|
localStorage.setItem('folders-view-mode', viewMode);
|
|
72
67
|
}, [viewMode]);
|
|
73
68
|
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
localStorage.setItem('folders-compact-mode', compactMode.toString());
|
|
76
|
-
}, [compactMode]);
|
|
77
|
-
|
|
78
69
|
useEffect(() => {
|
|
79
70
|
loadData();
|
|
80
71
|
}, [sortBy, scope, page, pageSize]);
|
|
@@ -196,104 +187,54 @@ export default function Folders() {
|
|
|
196
187
|
|
|
197
188
|
return (
|
|
198
189
|
<div className="space-y-6 pb-24">
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
</div>
|
|
248
|
-
<div className="flex items-center gap-2">
|
|
249
|
-
<Select
|
|
250
|
-
value={String(pageSize)}
|
|
251
|
-
onChange={(value) => updateParams({ limit: value })}
|
|
252
|
-
options={PAGE_SIZE_OPTIONS.map((n) => ({ value: String(n), label: String(n) }))}
|
|
253
|
-
className="min-w-[80px]"
|
|
254
|
-
/>
|
|
255
|
-
<span className="text-sm text-muted-foreground whitespace-nowrap">{t('bookmarks.perPage')}</span>
|
|
256
|
-
</div>
|
|
257
|
-
{/* View Mode Toggle */}
|
|
258
|
-
<div className="flex items-center gap-2 border-l border-gray-200 dark:border-gray-700 pl-3 ml-auto">
|
|
259
|
-
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
|
|
260
|
-
<button
|
|
261
|
-
onClick={() => setViewMode('card')}
|
|
262
|
-
className={`p-1.5 rounded transition-colors ${
|
|
263
|
-
viewMode === 'card'
|
|
264
|
-
? 'bg-card text-primary shadow-sm'
|
|
265
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
266
|
-
}`}
|
|
267
|
-
title={t('folders.viewCard')}
|
|
268
|
-
>
|
|
269
|
-
<LayoutGrid className="h-4 w-4" />
|
|
270
|
-
</button>
|
|
271
|
-
<button
|
|
272
|
-
onClick={() => setViewMode('list')}
|
|
273
|
-
className={`p-1.5 rounded transition-colors ${
|
|
274
|
-
viewMode === 'list'
|
|
275
|
-
? 'bg-card text-primary shadow-sm'
|
|
276
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
277
|
-
}`}
|
|
278
|
-
title={t('folders.viewList')}
|
|
279
|
-
>
|
|
280
|
-
<List className="h-4 w-4" />
|
|
281
|
-
</button>
|
|
282
|
-
</div>
|
|
283
|
-
<button
|
|
284
|
-
onClick={() => setCompactMode(!compactMode)}
|
|
285
|
-
className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${
|
|
286
|
-
compactMode
|
|
287
|
-
? 'bg-primary/20 text-primary'
|
|
288
|
-
: 'bg-muted text-muted-foreground hover:bg-accent'
|
|
289
|
-
}`}
|
|
290
|
-
title={t('folders.compactMode')}
|
|
291
|
-
>
|
|
292
|
-
{t('folders.compactMode')}
|
|
293
|
-
</button>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
190
|
+
<CollectionToolbar
|
|
191
|
+
title={t('folders.title')}
|
|
192
|
+
count={totalFolders}
|
|
193
|
+
subtitle={
|
|
194
|
+
hasActiveFilters || totalFolders > pageSize
|
|
195
|
+
? t('bookmarks.showingXOfY', { x: sortedFolders.length, y: totalFolders })
|
|
196
|
+
: undefined
|
|
197
|
+
}
|
|
198
|
+
tabs={{
|
|
199
|
+
value: scope,
|
|
200
|
+
onChange: (s) => updateParams({ scope: s === 'all' ? undefined : s }),
|
|
201
|
+
options: [
|
|
202
|
+
{ value: 'all', label: t('bookmarks.scopeAll') },
|
|
203
|
+
{ value: 'mine', label: t('bookmarks.scopeMine') },
|
|
204
|
+
{ value: 'shared_with_me', label: t('common.scopeSharedWithMe') },
|
|
205
|
+
{ value: 'shared_by_me', label: t('common.scopeSharedByMe') },
|
|
206
|
+
],
|
|
207
|
+
ariaLabel: t('bookmarks.scopeAll'),
|
|
208
|
+
}}
|
|
209
|
+
createButton={{ label: t('folders.create'), onClick: handleCreate }}
|
|
210
|
+
filterChips={{
|
|
211
|
+
chips: filterChips,
|
|
212
|
+
onRemove: handleRemoveFilter,
|
|
213
|
+
onClearAll: handleResetFilters,
|
|
214
|
+
clearAllLabel: t('bookmarks.clearAllFilters'),
|
|
215
|
+
clearAllAriaLabel: t('bookmarks.clearAllFilters'),
|
|
216
|
+
}}
|
|
217
|
+
sort={{
|
|
218
|
+
value: sortBy,
|
|
219
|
+
onChange: (value) => updateParams({ sort: value as SortOption }),
|
|
220
|
+
options: sortOptions,
|
|
221
|
+
className: 'min-w-[160px]',
|
|
222
|
+
}}
|
|
223
|
+
perPage={{
|
|
224
|
+
value: pageSize,
|
|
225
|
+
onChange: (value) => {
|
|
226
|
+
updateParams({ limit: String(value) });
|
|
227
|
+
},
|
|
228
|
+
options: [...PAGE_SIZE_OPTIONS],
|
|
229
|
+
label: t('bookmarks.perPage'),
|
|
230
|
+
}}
|
|
231
|
+
viewMode={{
|
|
232
|
+
value: viewMode,
|
|
233
|
+
onChange: setViewMode,
|
|
234
|
+
cardLabel: t('folders.viewCard'),
|
|
235
|
+
listLabel: t('folders.viewList'),
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
297
238
|
|
|
298
239
|
{/* Folders Display */}
|
|
299
240
|
{sortedFolders.length === 0 ? (
|
|
@@ -321,109 +262,102 @@ export default function Folders() {
|
|
|
321
262
|
/>
|
|
322
263
|
)
|
|
323
264
|
) : viewMode === 'card' ? (
|
|
324
|
-
<div className=
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
key={folder.id}
|
|
332
|
-
className={`group bg-card rounded-lg border border-border hover:border-primary/70 hover:bg-muted/50 hover:shadow-md transition-all duration-200 flex flex-col h-full min-h-0 ${compactMode ? 'p-2.5 min-h-[160px]' : 'p-2.5 min-h-[140px]'}`}
|
|
333
|
-
>
|
|
334
|
-
<Link
|
|
335
|
-
to={`${prefix}/bookmarks?folder_id=${folder.id}`}
|
|
336
|
-
className="flex-1 flex flex-col min-w-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
|
|
265
|
+
<div className="grid gap-3 items-stretch [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]">
|
|
266
|
+
{sortedFolders.map((folder) => {
|
|
267
|
+
const isShared = (folder.shared_teams && folder.shared_teams.length > 0) || (folder.shared_users && folder.shared_users.length > 0);
|
|
268
|
+
return (
|
|
269
|
+
<Card
|
|
270
|
+
key={folder.id}
|
|
271
|
+
className="group relative flex flex-col h-[101px] cursor-pointer rounded-lg border bg-card/95 dark:bg-card/90 transition-[border-color,box-shadow] duration-150 border-border/80 hover:border-primary/80 hover:shadow-[0_2px_6px_rgba(0,0,0,0.06)] dark:border-border/70 dark:hover:border-primary/80 dark:hover:shadow-[0_2px_6px_rgba(0,0,0,0.25)] px-3 pt-0 pb-1.5 focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
|
|
337
272
|
>
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
</h3>
|
|
347
|
-
{folder.shared_teams && folder.shared_teams.length > 0 && (
|
|
348
|
-
<Tooltip
|
|
349
|
-
content={
|
|
350
|
-
<div className="space-y-1">
|
|
351
|
-
<div className="font-semibold mb-1">{t('folders.sharedWith')}</div>
|
|
352
|
-
{folder.shared_teams.map((team) => (
|
|
353
|
-
<div key={team.id} className="text-xs">• {team.name}</div>
|
|
354
|
-
))}
|
|
355
|
-
{folder.shared_users && folder.shared_users.length > 0 && (
|
|
356
|
-
folder.shared_users.map((user) => (
|
|
357
|
-
<div key={user.id} className="text-xs">• {user.name || user.email}</div>
|
|
358
|
-
))
|
|
359
|
-
)}
|
|
360
|
-
</div>
|
|
361
|
-
}
|
|
362
|
-
>
|
|
363
|
-
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-md border border-green-200 dark:border-green-800/50 cursor-help">
|
|
364
|
-
<Share2 className="h-3 w-3" />
|
|
365
|
-
{t('folders.shared')}
|
|
366
|
-
</span>
|
|
367
|
-
</Tooltip>
|
|
368
|
-
)}
|
|
369
|
-
</div>
|
|
273
|
+
<Link
|
|
274
|
+
to={`${prefix}/bookmarks?folder_id=${folder.id}`}
|
|
275
|
+
className="absolute inset-0 rounded-lg z-0 focus:outline-none"
|
|
276
|
+
aria-label={folder.name}
|
|
277
|
+
/>
|
|
278
|
+
<header className="flex-shrink-0 flex items-center gap-1.5 min-w-0 pt-3 relative z-10">
|
|
279
|
+
<div className="flex-shrink-0 w-7 h-7 rounded-md bg-background/90 dark:bg-muted/20 flex items-center justify-center border border-border/50 overflow-hidden">
|
|
280
|
+
<FolderIcon iconName={folder.icon} size={16} className="text-primary" />
|
|
370
281
|
</div>
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
282
|
+
<div className="flex-1 min-w-0 min-h-0 overflow-hidden">
|
|
283
|
+
<h3 className="text-sm font-semibold text-foreground line-clamp-2 break-words leading-snug tracking-tight min-h-0">
|
|
284
|
+
{folder.name}
|
|
285
|
+
</h3>
|
|
286
|
+
</div>
|
|
287
|
+
</header>
|
|
288
|
+
{isShared && (
|
|
289
|
+
<div className="flex-shrink-0 mt-1.5 relative z-10">
|
|
290
|
+
<Tooltip
|
|
291
|
+
content={
|
|
292
|
+
<div className="space-y-1">
|
|
293
|
+
<div className="font-semibold mb-1">{t('folders.sharedWith')}</div>
|
|
294
|
+
{folder.shared_teams?.map((team) => (
|
|
295
|
+
<div key={team.id} className="text-xs">• {team.name}</div>
|
|
296
|
+
))}
|
|
297
|
+
{folder.shared_users?.map((user) => (
|
|
298
|
+
<div key={user.id} className="text-xs">• {user.name || user.email}</div>
|
|
299
|
+
))}
|
|
300
|
+
</div>
|
|
301
|
+
}
|
|
302
|
+
>
|
|
303
|
+
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-medium bg-emerald-500/10 dark:bg-emerald-500/10 text-muted-foreground rounded cursor-help">
|
|
304
|
+
<Share2 className="h-2.5 w-2.5" />
|
|
305
|
+
{t('folders.shared')}
|
|
306
|
+
</span>
|
|
307
|
+
</Tooltip>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
<div className="flex-1 min-h-0" aria-hidden />
|
|
311
|
+
{folder.folder_type === 'own' && (
|
|
312
|
+
<footer className="flex-shrink-0 flex items-center justify-end gap-0.5 h-6 min-h-[24px] pt-2.5 relative z-10 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150 w-[76px] ml-auto">
|
|
313
|
+
<Button
|
|
314
|
+
variant="ghost"
|
|
315
|
+
size="sm"
|
|
316
|
+
icon={Share2}
|
|
317
|
+
iconClassName="h-3.5 w-3.5 stroke-[1.5]"
|
|
318
|
+
onClick={(e) => { e.preventDefault(); e.stopPropagation(); setSharingFolder(folder); setShareDialogOpen(true); }}
|
|
319
|
+
className="h-6 w-6 p-0 min-w-6 text-muted-foreground hover:text-foreground"
|
|
320
|
+
title={t('sharing.shareFolder')}
|
|
321
|
+
/>
|
|
322
|
+
<Button
|
|
323
|
+
variant="ghost"
|
|
324
|
+
size="sm"
|
|
325
|
+
icon={Edit}
|
|
326
|
+
iconClassName="h-3.5 w-3.5 stroke-[1.5]"
|
|
327
|
+
onClick={(e) => { e.preventDefault(); e.stopPropagation(); handleEdit(folder); }}
|
|
328
|
+
className="h-6 w-6 p-0 min-w-6 text-muted-foreground hover:text-foreground"
|
|
329
|
+
title={t('common.edit')}
|
|
330
|
+
/>
|
|
331
|
+
<Button
|
|
332
|
+
variant="ghost"
|
|
333
|
+
size="sm"
|
|
334
|
+
icon={Trash2}
|
|
335
|
+
iconClassName="h-3.5 w-3.5 stroke-[1.5]"
|
|
336
|
+
onClick={(e) => { e.preventDefault(); e.stopPropagation(); handleDelete(folder.id); }}
|
|
337
|
+
className="h-6 w-6 p-0 min-w-6 text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
|
|
338
|
+
title={t('common.delete')}
|
|
339
|
+
/>
|
|
340
|
+
</footer>
|
|
341
|
+
)}
|
|
342
|
+
</Card>
|
|
343
|
+
);
|
|
344
|
+
})}
|
|
407
345
|
</div>
|
|
408
346
|
) : (
|
|
409
347
|
<div className="overflow-x-auto bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
410
348
|
<table className="w-full">
|
|
411
349
|
<thead className="bg-gray-50 dark:bg-gray-900/50 border-b border-gray-200 dark:border-gray-700">
|
|
412
350
|
<tr>
|
|
413
|
-
<th className=
|
|
351
|
+
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">
|
|
414
352
|
{t('folders.name')}
|
|
415
353
|
</th>
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
{t('folders.shared')}
|
|
424
|
-
</th>
|
|
425
|
-
)}
|
|
426
|
-
<th className={`${compactMode ? 'px-2 py-1.5' : 'px-4 py-3'} text-right ${compactMode ? 'text-[10px]' : 'text-xs'} font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide`}>
|
|
354
|
+
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">
|
|
355
|
+
{t('bookmarks.title')}
|
|
356
|
+
</th>
|
|
357
|
+
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">
|
|
358
|
+
{t('folders.shared')}
|
|
359
|
+
</th>
|
|
360
|
+
<th className="px-4 py-3 text-right text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">
|
|
427
361
|
{t('common.actions')}
|
|
428
362
|
</th>
|
|
429
363
|
</tr>
|
|
@@ -432,75 +366,71 @@ export default function Folders() {
|
|
|
432
366
|
{sortedFolders.map((folder) => (
|
|
433
367
|
<tr
|
|
434
368
|
key={folder.id}
|
|
435
|
-
className=
|
|
369
|
+
className="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
|
436
370
|
>
|
|
437
|
-
<td className=
|
|
371
|
+
<td className="px-4 py-3">
|
|
438
372
|
<Link
|
|
439
373
|
to={`${prefix}/bookmarks?folder_id=${folder.id}`}
|
|
440
|
-
className=
|
|
374
|
+
className="flex items-center gap-3 hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
|
|
441
375
|
>
|
|
442
|
-
<div className=
|
|
443
|
-
<FolderIcon iconName={folder.icon} size={
|
|
376
|
+
<div className="flex-shrink-0 w-8 h-8 rounded-lg bg-primary/20 flex items-center justify-center border border-primary/30">
|
|
377
|
+
<FolderIcon iconName={folder.icon} size={16} className="text-primary" />
|
|
444
378
|
</div>
|
|
445
|
-
<div className=
|
|
379
|
+
<div className="font-medium text-gray-900 dark:text-white text-[15px]">
|
|
446
380
|
{folder.name}
|
|
447
381
|
</div>
|
|
448
382
|
</Link>
|
|
449
383
|
</td>
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
)}
|
|
485
|
-
</td>
|
|
486
|
-
)}
|
|
487
|
-
<td className={`${compactMode ? 'px-2 py-1.5' : 'px-4 py-3'}`}>
|
|
384
|
+
<td className="px-4 py-3 text-xs text-muted-foreground">—</td>
|
|
385
|
+
<td className="px-4 py-3">
|
|
386
|
+
{folder.shared_teams && folder.shared_teams.length > 0 ? (
|
|
387
|
+
<Tooltip
|
|
388
|
+
content={
|
|
389
|
+
<div className="space-y-1">
|
|
390
|
+
<div className="font-semibold mb-1">{t('folders.sharedWith')}</div>
|
|
391
|
+
{folder.shared_teams.map((team) => (
|
|
392
|
+
<div key={team.id} className="text-xs">
|
|
393
|
+
• {team.name}
|
|
394
|
+
</div>
|
|
395
|
+
))}
|
|
396
|
+
{folder.shared_users && folder.shared_users.length > 0 && (
|
|
397
|
+
<>
|
|
398
|
+
{folder.shared_users.map((user) => (
|
|
399
|
+
<div key={user.id} className="text-xs">
|
|
400
|
+
• {user.name || user.email}
|
|
401
|
+
</div>
|
|
402
|
+
))}
|
|
403
|
+
</>
|
|
404
|
+
)}
|
|
405
|
+
</div>
|
|
406
|
+
}
|
|
407
|
+
>
|
|
408
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-md cursor-help">
|
|
409
|
+
<Share2 className="h-3 w-3" />
|
|
410
|
+
{t('folders.shared')}
|
|
411
|
+
</span>
|
|
412
|
+
</Tooltip>
|
|
413
|
+
) : (
|
|
414
|
+
<span className="text-xs text-gray-500 dark:text-gray-400">-</span>
|
|
415
|
+
)}
|
|
416
|
+
</td>
|
|
417
|
+
<td className="px-4 py-3">
|
|
488
418
|
{folder.folder_type === 'own' && (
|
|
489
|
-
<div className=
|
|
419
|
+
<div className="flex items-center justify-end gap-2">
|
|
490
420
|
<Button
|
|
491
421
|
variant="ghost"
|
|
492
422
|
size="sm"
|
|
493
423
|
icon={Share2}
|
|
494
424
|
onClick={() => { setSharingFolder(folder); setShareDialogOpen(true); }}
|
|
495
425
|
title={t('sharing.shareFolder')}
|
|
496
|
-
className=
|
|
426
|
+
className="px-2"
|
|
497
427
|
/>
|
|
498
428
|
<Button
|
|
499
429
|
variant="ghost"
|
|
500
430
|
size="sm"
|
|
501
431
|
icon={Edit}
|
|
502
432
|
onClick={() => handleEdit(folder)}
|
|
503
|
-
className=
|
|
433
|
+
className="px-2"
|
|
504
434
|
/>
|
|
505
435
|
<Button
|
|
506
436
|
variant="ghost"
|
|
@@ -508,7 +438,7 @@ export default function Folders() {
|
|
|
508
438
|
icon={Trash2}
|
|
509
439
|
onClick={() => handleDelete(folder.id)}
|
|
510
440
|
title={t('common.delete')}
|
|
511
|
-
className=
|
|
441
|
+
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 px-2"
|
|
512
442
|
/>
|
|
513
443
|
</div>
|
|
514
444
|
)}
|