Files
clienthub/src/components/admin/DocumentRow.tsx
T
Simone Cavalli 3582e26970 feat: document edit inline + client dashboard sidebar layout
- actions.ts: add updateDocument server action (label + url, Zod validated)
- DocumentRow: Client Component with hover-reveal edit/remove buttons,
  inline edit form with pre-filled fields and cancel/save
- DocumentsTab: use DocumentRow, remove variant dependency
- client-dashboard: two-column layout (sidebar left on lg+):
  sidebar = payments + documents + notes (sticky top)
  main = brief + phases toggle (timeline / kanban)
  mobile: main first, sidebar below (order-1/order-2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:24:49 +02:00

114 lines
3.0 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { updateDocument, deleteDocument } from "@/app/admin/clients/[id]/actions";
import type { Document } from "@/db/schema";
export function DocumentRow({
doc,
clientId,
}: {
doc: Document;
clientId: string;
}) {
const [editing, setEditing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [, startTransition] = useTransition();
const router = useRouter();
function handleSave(fd: FormData) {
setError(null);
startTransition(async () => {
try {
await updateDocument(doc.id, clientId, fd);
setEditing(false);
router.refresh();
} catch (e) {
setError(e instanceof Error ? e.message : "Errore nel salvataggio");
}
});
}
function handleDelete() {
startTransition(async () => {
await deleteDocument(doc.id, clientId);
router.refresh();
});
}
if (editing) {
return (
<form
action={handleSave}
className="bg-white border-2 border-[#1A463C]/30 rounded-lg px-4 py-3 space-y-2"
>
<Input
name="label"
defaultValue={doc.label}
placeholder="Nome / etichetta"
required
className="text-sm"
/>
<Input
name="url"
defaultValue={doc.url}
type="url"
placeholder="https://..."
required
className="text-sm"
/>
{error && <p className="text-xs text-red-600">{error}</p>}
<div className="flex gap-2 pt-1">
<Button type="submit" size="sm">
Salva
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => { setEditing(false); setError(null); }}
className="text-[#71717a]"
>
Annulla
</Button>
</div>
</form>
);
}
return (
<div className="flex items-center justify-between bg-white border border-[#e5e7eb] rounded-lg px-4 py-3 group">
<a
href={doc.url}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-[#1A463C] hover:underline font-medium truncate min-w-0 mr-3"
>
{doc.label}
</a>
<div className="flex items-center gap-1 shrink-0">
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setEditing(true)}
className="text-xs text-[#71717a] hover:text-[#1A463C] opacity-0 group-hover:opacity-100 transition-opacity"
>
Modifica
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={handleDelete}
className="text-xs text-red-400 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity"
>
Rimuovi
</Button>
</div>
</div>
);
}