feat(registratura): add Conex (reply) + Inchide buttons, reorder completari last
- Conex button on table rows (Reply icon, blue) — opens new entry with threadParentId pre-set + flipped direction - Conex button on detail panel — same behavior - Inchide button on table rows (CheckCircle2 icon, green) — only for open entries - replyTo prop on RegistryEntryForm: pre-sets threadParentId + direction flip (intrat→iesit, iesit→intrat) - Card header shows "Conex la BTG-0042/2026" with blue badge when replying - Completari moved to last position in deadline category order Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,8 @@ export function RegistraturaModule() {
|
|||||||
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
||||||
const [editingEntry, setEditingEntry] = useState<RegistryEntry | null>(null);
|
const [editingEntry, setEditingEntry] = useState<RegistryEntry | null>(null);
|
||||||
const [viewingEntry, setViewingEntry] = useState<RegistryEntry | null>(null);
|
const [viewingEntry, setViewingEntry] = useState<RegistryEntry | null>(null);
|
||||||
|
/** Entry to reply to (conex) — pre-sets threadParentId in new form */
|
||||||
|
const [replyToEntry, setReplyToEntry] = useState<RegistryEntry | null>(null);
|
||||||
const [closingId, setClosingId] = useState<string | null>(null);
|
const [closingId, setClosingId] = useState<string | null>(null);
|
||||||
const [linkCheckId, setLinkCheckId] = useState<string | null>(null);
|
const [linkCheckId, setLinkCheckId] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -136,6 +138,7 @@ export function RegistraturaModule() {
|
|||||||
data: Omit<RegistryEntry, "id" | "number" | "createdAt" | "updatedAt">,
|
data: Omit<RegistryEntry, "id" | "number" | "createdAt" | "updatedAt">,
|
||||||
) => {
|
) => {
|
||||||
await addEntry(data);
|
await addEntry(data);
|
||||||
|
setReplyToEntry(null);
|
||||||
setViewMode("list");
|
setViewMode("list");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,6 +155,13 @@ export function RegistraturaModule() {
|
|||||||
setViewingEntry(full ?? entry);
|
setViewingEntry(full ?? entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReply = (entry: RegistryEntry) => {
|
||||||
|
setReplyToEntry(entry);
|
||||||
|
setViewingEntry(null);
|
||||||
|
setEditingEntry(null);
|
||||||
|
setViewMode("add");
|
||||||
|
};
|
||||||
|
|
||||||
const handleNavigateEntry = async (entry: RegistryEntry) => {
|
const handleNavigateEntry = async (entry: RegistryEntry) => {
|
||||||
const full = await loadFullEntry(entry.id);
|
const full = await loadFullEntry(entry.id);
|
||||||
setEditingEntry(full ?? entry);
|
setEditingEntry(full ?? entry);
|
||||||
@@ -206,6 +216,7 @@ export function RegistraturaModule() {
|
|||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setViewMode("list");
|
setViewMode("list");
|
||||||
setEditingEntry(null);
|
setEditingEntry(null);
|
||||||
|
setReplyToEntry(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Dashboard deadline resolve/chain handlers ──
|
// ── Dashboard deadline resolve/chain handlers ──
|
||||||
@@ -384,6 +395,7 @@ export function RegistraturaModule() {
|
|||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
onClose={handleCloseRequest}
|
onClose={handleCloseRequest}
|
||||||
|
onReply={handleReply}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!loading && (
|
{!loading && (
|
||||||
@@ -398,15 +410,27 @@ export function RegistraturaModule() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
Înregistrare nouă
|
{replyToEntry ? (
|
||||||
|
<>
|
||||||
|
Conex la {replyToEntry.number}
|
||||||
|
<Badge variant="outline" className="text-xs text-blue-600 border-blue-300">
|
||||||
|
Raspuns
|
||||||
|
</Badge>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Inregistrare noua
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
Nr. auto
|
Nr. auto
|
||||||
</Badge>
|
</Badge>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<RegistryEntryForm
|
<RegistryEntryForm
|
||||||
allEntries={allEntries}
|
allEntries={allEntries}
|
||||||
|
replyTo={replyToEntry ?? undefined}
|
||||||
onSubmit={handleAdd}
|
onSubmit={handleAdd}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onCreateContact={handleCreateContact}
|
onCreateContact={handleCreateContact}
|
||||||
@@ -445,6 +469,7 @@ export function RegistraturaModule() {
|
|||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
onClose={handleCloseRequest}
|
onClose={handleCloseRequest}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
onReply={handleReply}
|
||||||
allEntries={allEntries}
|
allEntries={allEntries}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
User,
|
User,
|
||||||
X,
|
X,
|
||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
|
Reply,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import { Badge } from "@/shared/components/ui/badge";
|
import { Badge } from "@/shared/components/ui/badge";
|
||||||
@@ -49,6 +50,8 @@ interface RegistryEntryDetailProps {
|
|||||||
onEdit: (entry: RegistryEntry) => void;
|
onEdit: (entry: RegistryEntry) => void;
|
||||||
onClose: (id: string) => void;
|
onClose: (id: string) => void;
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
|
/** Create a new entry linked as reply (conex) to this entry */
|
||||||
|
onReply?: (entry: RegistryEntry) => void;
|
||||||
allEntries: RegistryEntry[];
|
allEntries: RegistryEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +140,7 @@ export function RegistryEntryDetail({
|
|||||||
onEdit,
|
onEdit,
|
||||||
onClose,
|
onClose,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onReply,
|
||||||
allEntries,
|
allEntries,
|
||||||
}: RegistryEntryDetailProps) {
|
}: RegistryEntryDetailProps) {
|
||||||
const [previewIndex, setPreviewIndex] = useState<number | null>(null);
|
const [previewIndex, setPreviewIndex] = useState<number | null>(null);
|
||||||
@@ -200,6 +204,19 @@ export function RegistryEntryDetail({
|
|||||||
>
|
>
|
||||||
<Pencil className="mr-1.5 h-3.5 w-3.5" /> Editează
|
<Pencil className="mr-1.5 h-3.5 w-3.5" /> Editează
|
||||||
</Button>
|
</Button>
|
||||||
|
{onReply && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="text-blue-600 border-blue-300 hover:bg-blue-50 dark:border-blue-700 dark:hover:bg-blue-950/30"
|
||||||
|
onClick={() => {
|
||||||
|
onOpenChange(false);
|
||||||
|
onReply(entry);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Reply className="mr-1.5 h-3.5 w-3.5" /> Conex
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{entry.status === "deschis" && (
|
{entry.status === "deschis" && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -210,7 +227,7 @@ export function RegistryEntryDetail({
|
|||||||
onClose(entry.id);
|
onClose(entry.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CheckCircle2 className="mr-1.5 h-3.5 w-3.5" /> Închide
|
<CheckCircle2 className="mr-1.5 h-3.5 w-3.5" /> Inchide
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ import { getDeadlineType } from "../services/deadline-catalog";
|
|||||||
|
|
||||||
interface RegistryEntryFormProps {
|
interface RegistryEntryFormProps {
|
||||||
initial?: RegistryEntry;
|
initial?: RegistryEntry;
|
||||||
|
/** Pre-fill as reply (conex) to this entry — sets threadParentId, flips direction */
|
||||||
|
replyTo?: RegistryEntry;
|
||||||
allEntries?: RegistryEntry[];
|
allEntries?: RegistryEntry[];
|
||||||
onSubmit: (
|
onSubmit: (
|
||||||
data: Omit<RegistryEntry, "id" | "number" | "createdAt" | "updatedAt">,
|
data: Omit<RegistryEntry, "id" | "number" | "createdAt" | "updatedAt">,
|
||||||
@@ -106,6 +108,7 @@ interface RegistryEntryFormProps {
|
|||||||
|
|
||||||
export function RegistryEntryForm({
|
export function RegistryEntryForm({
|
||||||
initial,
|
initial,
|
||||||
|
replyTo,
|
||||||
allEntries,
|
allEntries,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
@@ -150,8 +153,12 @@ export function RegistryEntryForm({
|
|||||||
return sorted;
|
return sorted;
|
||||||
}, [docTypeTags, localCustomTypes]);
|
}, [docTypeTags, localCustomTypes]);
|
||||||
|
|
||||||
|
// When replyTo is provided, flip direction (intrat→iesit, iesit→intrat)
|
||||||
|
const replyDirection: RegistryDirection | undefined = replyTo
|
||||||
|
? replyTo.direction === "intrat" ? "iesit" : "intrat"
|
||||||
|
: undefined;
|
||||||
const [direction, setDirection] = useState<RegistryDirection>(
|
const [direction, setDirection] = useState<RegistryDirection>(
|
||||||
initial?.direction ?? "intrat",
|
initial?.direction ?? replyDirection ?? "intrat",
|
||||||
);
|
);
|
||||||
const defaultDocType = initial?.documentType ?? (direction === "intrat" ? "aviz" : "cerere");
|
const defaultDocType = initial?.documentType ?? (direction === "intrat" ? "aviz" : "cerere");
|
||||||
const [documentType, setDocumentType] = useState<DocumentType>(defaultDocType);
|
const [documentType, setDocumentType] = useState<DocumentType>(defaultDocType);
|
||||||
@@ -182,7 +189,7 @@ export function RegistryEntryForm({
|
|||||||
initial?.assigneeContactId ?? "",
|
initial?.assigneeContactId ?? "",
|
||||||
);
|
);
|
||||||
const [threadParentId, setThreadParentId] = useState(
|
const [threadParentId, setThreadParentId] = useState(
|
||||||
initial?.threadParentId ?? "",
|
initial?.threadParentId ?? replyTo?.id ?? "",
|
||||||
);
|
);
|
||||||
const [notes, setNotes] = useState(initial?.notes ?? "");
|
const [notes, setNotes] = useState(initial?.notes ?? "");
|
||||||
const [linkedEntryIds, setLinkedEntryIds] = useState<string[]>(
|
const [linkedEntryIds, setLinkedEntryIds] = useState<string[]>(
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
User,
|
User,
|
||||||
Settings2,
|
Settings2,
|
||||||
Paperclip,
|
Paperclip,
|
||||||
|
Reply,
|
||||||
|
CheckCircle2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import { Badge } from "@/shared/components/ui/badge";
|
import { Badge } from "@/shared/components/ui/badge";
|
||||||
@@ -39,6 +41,8 @@ interface RegistryTableProps {
|
|||||||
onEdit: (entry: RegistryEntry) => void;
|
onEdit: (entry: RegistryEntry) => void;
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
onClose: (id: string) => void;
|
onClose: (id: string) => void;
|
||||||
|
/** Create a new entry linked as reply (conex) to this entry */
|
||||||
|
onReply?: (entry: RegistryEntry) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Column definitions ──
|
// ── Column definitions ──
|
||||||
@@ -175,6 +179,7 @@ export function RegistryTable({
|
|||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
onClose,
|
onClose,
|
||||||
|
onReply,
|
||||||
}: RegistryTableProps) {
|
}: RegistryTableProps) {
|
||||||
const [visibleCols, setVisibleCols] = useState<Set<ColumnId>>(
|
const [visibleCols, setVisibleCols] = useState<Set<ColumnId>>(
|
||||||
() => DEFAULT_VISIBLE,
|
() => DEFAULT_VISIBLE,
|
||||||
@@ -465,10 +470,38 @@ export function RegistryTable({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onEdit(entry);
|
onEdit(entry);
|
||||||
}}
|
}}
|
||||||
title="Editează"
|
title="Editeaza"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
{onReply && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-blue-600"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onReply(entry);
|
||||||
|
}}
|
||||||
|
title="Conex — creeaza raspuns"
|
||||||
|
>
|
||||||
|
<Reply className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{entry.status === "deschis" && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-green-600"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onClose(entry.id);
|
||||||
|
}}
|
||||||
|
title="Inchide"
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ export const DIRECTION_CATEGORIES: Record<
|
|||||||
RegistryDirection,
|
RegistryDirection,
|
||||||
DeadlineCategory[]
|
DeadlineCategory[]
|
||||||
> = {
|
> = {
|
||||||
iesit: ["certificat", "avize", "completari", "urbanism", "autorizare"],
|
iesit: ["certificat", "avize", "urbanism", "autorizare", "completari"],
|
||||||
intrat: ["contestatie"],
|
intrat: ["contestatie"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user