feat(registratura): rework with company-prefixed numbering, directions, deadlines, attachments

- Company-specific numbering (B-0001/2026, US-0001/2026, SDT-0001/2026)
- Direction: Intrat/Ieșit replaces old 3-way type
- 9 document types: Contract, Ofertă, Factură, Scrisoare, etc.
- Status simplified to Deschis/Închis with cascade close for linked entries
- Address Book autocomplete for sender/recipient
- Deadline tracking with overdue day counter
- File attachment support (base64 encoding)
- Linked entries system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Marius Tarau
2026-02-18 06:35:23 +02:00
parent 84d9db4515
commit 98eda56035
8 changed files with 550 additions and 109 deletions

View File

@@ -5,10 +5,14 @@ import { Plus } from 'lucide-react';
import { Button } from '@/shared/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card';
import { Badge } from '@/shared/components/ui/badge';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
} from '@/shared/components/ui/dialog';
import { useRegistry } from '../hooks/use-registry';
import { RegistryFilters } from './registry-filters';
import { RegistryTable } from './registry-table';
import { RegistryEntryForm } from './registry-entry-form';
import { getOverdueDays } from '../services/registry-service';
import type { RegistryEntry } from '../types';
type ViewMode = 'list' | 'add' | 'edit';
@@ -16,11 +20,12 @@ type ViewMode = 'list' | 'add' | 'edit';
export function RegistraturaModule() {
const {
entries, allEntries, loading, filters, updateFilter,
addEntry, updateEntry, removeEntry,
addEntry, updateEntry, removeEntry, closeEntry,
} = useRegistry();
const [viewMode, setViewMode] = useState<ViewMode>('list');
const [editingEntry, setEditingEntry] = useState<RegistryEntry | null>(null);
const [closingId, setClosingId] = useState<string | null>(null);
const handleAdd = async (data: Omit<RegistryEntry, 'id' | 'number' | 'createdAt' | 'updatedAt'>) => {
await addEntry(data);
@@ -43,6 +48,22 @@ export function RegistraturaModule() {
await removeEntry(id);
};
const handleCloseRequest = (id: string) => {
const entry = allEntries.find((e) => e.id === id);
if (entry && entry.linkedEntryIds.length > 0) {
setClosingId(id);
} else {
closeEntry(id, false);
}
};
const handleCloseConfirm = (closeLinked: boolean) => {
if (closingId) {
closeEntry(closingId, closeLinked);
setClosingId(null);
}
};
const handleCancel = () => {
setViewMode('list');
setEditingEntry(null);
@@ -50,18 +71,24 @@ export function RegistraturaModule() {
// Stats
const total = allEntries.length;
const incoming = allEntries.filter((e) => e.type === 'incoming').length;
const outgoing = allEntries.filter((e) => e.type === 'outgoing').length;
const inProgress = allEntries.filter((e) => e.status === 'in-progress').length;
const open = allEntries.filter((e) => e.status === 'deschis').length;
const overdue = allEntries.filter((e) => {
if (e.status !== 'deschis') return false;
const days = getOverdueDays(e.deadline);
return days !== null && days > 0;
}).length;
const intrat = allEntries.filter((e) => e.direction === 'intrat').length;
const closingEntry = closingId ? allEntries.find((e) => e.id === closingId) : null;
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatCard label="Total" value={total} />
<StatCard label="Intrare" value={incoming} />
<StatCard label="Ieșire" value={outgoing} />
<StatCard label="În lucru" value={inProgress} />
<StatCard label="Deschise" value={open} />
<StatCard label="Depășite" value={overdue} variant={overdue > 0 ? 'destructive' : undefined} />
<StatCard label="Intrate" value={intrat} />
</div>
{viewMode === 'list' && (
@@ -78,6 +105,7 @@ export function RegistraturaModule() {
loading={loading}
onEdit={handleEdit}
onDelete={handleDelete}
onClose={handleCloseRequest}
/>
{!loading && (
@@ -97,7 +125,11 @@ export function RegistraturaModule() {
</CardTitle>
</CardHeader>
<CardContent>
<RegistryEntryForm onSubmit={handleAdd} onCancel={handleCancel} />
<RegistryEntryForm
allEntries={allEntries}
onSubmit={handleAdd}
onCancel={handleCancel}
/>
</CardContent>
</Card>
)}
@@ -108,20 +140,51 @@ export function RegistraturaModule() {
<CardTitle>Editare {editingEntry.number}</CardTitle>
</CardHeader>
<CardContent>
<RegistryEntryForm initial={editingEntry} onSubmit={handleUpdate} onCancel={handleCancel} />
<RegistryEntryForm
initial={editingEntry}
allEntries={allEntries}
onSubmit={handleUpdate}
onCancel={handleCancel}
/>
</CardContent>
</Card>
)}
{/* Close confirmation dialog */}
<Dialog open={closingId !== null} onOpenChange={(open) => { if (!open) setClosingId(null); }}>
<DialogContent>
<DialogHeader>
<DialogTitle>Închide înregistrarea</DialogTitle>
</DialogHeader>
<div className="py-2">
<p className="text-sm">
Această înregistrare are {closingEntry?.linkedEntryIds.length ?? 0} înregistrări legate.
Vrei le închizi și pe acestea?
</p>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setClosingId(null)}>Anulează</Button>
<Button variant="secondary" onClick={() => handleCloseConfirm(false)}>
Doar aceasta
</Button>
<Button onClick={() => handleCloseConfirm(true)}>
Închide toate legate
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}
function StatCard({ label, value }: { label: string; value: number }) {
function StatCard({ label, value, variant }: { label: string; value: number; variant?: 'destructive' }) {
return (
<Card>
<CardContent className="p-4">
<p className="text-xs text-muted-foreground">{label}</p>
<p className="text-2xl font-bold">{value}</p>
<p className={`text-2xl font-bold ${variant === 'destructive' && value > 0 ? 'text-destructive' : ''}`}>
{value}
</p>
</CardContent>
</Card>
);