3582e26970
- 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>
114 lines
3.0 KiB
TypeScript
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>
|
|
);
|
|
} |