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:
@@ -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 să 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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user