feat: add duplicate functionality for SWOT items, enhance ActionPanel layout, and update SwotCard with duplicate action
This commit is contained in:
@@ -172,64 +172,24 @@ export function ActionPanel({
|
||||
Créez des actions en sélectionnant plusieurs items SWOT.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{actions.map((action) => (
|
||||
<div
|
||||
key={action.id}
|
||||
className="group rounded-lg border border-border bg-background p-4 transition-colors hover:border-primary/30"
|
||||
className="group flex flex-col rounded-lg border border-border bg-background p-4 transition-colors hover:border-primary/30"
|
||||
onMouseEnter={() => onActionHover(action.links.map((l) => l.swotItemId))}
|
||||
onMouseLeave={onActionLeave}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium text-foreground">{action.title}</h3>
|
||||
<Badge
|
||||
variant={
|
||||
action.priority === 2
|
||||
? 'destructive'
|
||||
: action.priority === 1
|
||||
? 'warning'
|
||||
: 'default'
|
||||
}
|
||||
>
|
||||
{priorityLabels[action.priority]}
|
||||
</Badge>
|
||||
</div>
|
||||
{action.description && (
|
||||
<p className="mt-1 text-sm text-muted">{action.description}</p>
|
||||
)}
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{action.links.map((link) => (
|
||||
<Badge
|
||||
key={link.id}
|
||||
variant={categoryBadgeVariant[link.swotItem.category]}
|
||||
className="text-xs"
|
||||
>
|
||||
{categoryShort[link.swotItem.category]}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={action.status}
|
||||
onChange={(e) => handleStatusChange(action, e.target.value)}
|
||||
className="rounded-lg border border-border bg-card px-2 py-1 text-sm text-foreground"
|
||||
disabled={isPending}
|
||||
>
|
||||
<option value="todo">{statusLabels.todo}</option>
|
||||
<option value="in_progress">{statusLabels.in_progress}</option>
|
||||
<option value="done">{statusLabels.done}</option>
|
||||
</select>
|
||||
|
||||
{/* Header with title & actions */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-medium text-foreground line-clamp-2">{action.title}</h3>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<button
|
||||
onClick={() => openEditModal(action)}
|
||||
className="rounded p-1.5 text-muted opacity-0 transition-opacity hover:bg-card-hover hover:text-foreground group-hover:opacity-100"
|
||||
className="rounded p-1 text-muted opacity-0 transition-opacity hover:bg-card-hover hover:text-foreground group-hover:opacity-100"
|
||||
aria-label="Modifier"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -238,13 +198,12 @@ export function ActionPanel({
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleDelete(action.id)}
|
||||
className="rounded p-1.5 text-muted opacity-0 transition-opacity hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100"
|
||||
className="rounded p-1 text-muted opacity-0 transition-opacity hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100"
|
||||
aria-label="Supprimer"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -255,6 +214,54 @@ export function ActionPanel({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{action.description && (
|
||||
<p className="mt-1 text-sm text-muted line-clamp-2">{action.description}</p>
|
||||
)}
|
||||
|
||||
{/* Linked Items */}
|
||||
<div className="mt-3 flex flex-wrap content-start gap-1.5 max-h-24 overflow-y-auto">
|
||||
{action.links.map((link) => (
|
||||
<Badge
|
||||
key={link.id}
|
||||
variant={categoryBadgeVariant[link.swotItem.category]}
|
||||
className="text-xs max-w-full h-fit"
|
||||
title={link.swotItem.content}
|
||||
>
|
||||
<span className="truncate">
|
||||
{link.swotItem.content.length > 25
|
||||
? link.swotItem.content.slice(0, 25) + '...'
|
||||
: link.swotItem.content}
|
||||
</span>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Footer with status & priority */}
|
||||
<div className="mt-3 flex items-center justify-between gap-2 border-t border-border pt-3">
|
||||
<Badge
|
||||
variant={
|
||||
action.priority === 2
|
||||
? 'destructive'
|
||||
: action.priority === 1
|
||||
? 'warning'
|
||||
: 'default'
|
||||
}
|
||||
>
|
||||
{priorityLabels[action.priority]}
|
||||
</Badge>
|
||||
<select
|
||||
value={action.status}
|
||||
onChange={(e) => handleStatusChange(action, e.target.value)}
|
||||
className="rounded border border-border bg-card px-2 py-1 text-xs text-foreground"
|
||||
disabled={isPending}
|
||||
>
|
||||
<option value="todo">{statusLabels.todo}</option>
|
||||
<option value="in_progress">{statusLabels.in_progress}</option>
|
||||
<option value="done">{statusLabels.done}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user