diff --git a/src/app/(modules)/hot-desk/page.tsx b/src/app/(modules)/hot-desk/page.tsx new file mode 100644 index 0000000..8398eb9 --- /dev/null +++ b/src/app/(modules)/hot-desk/page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { FeatureGate } from "@/core/feature-flags"; +import { useI18n } from "@/core/i18n"; +import { HotDeskModule } from "@/modules/hot-desk"; + +export default function HotDeskPage() { + const { t } = useI18n(); + + return ( + }> +
+
+

+ {t("hot-desk.title")} +

+

{t("hot-desk.description")}

+
+ +
+
+ ); +} + +function ModuleDisabled() { + return ( +
+

Modul dezactivat

+
+ ); +} diff --git a/src/config/flags.ts b/src/config/flags.ts index 265c015..357cf18 100644 --- a/src/config/flags.ts +++ b/src/config/flags.ts @@ -98,6 +98,14 @@ export const DEFAULT_FLAGS: FeatureFlag[] = [ category: "module", overridable: true, }, + { + key: "module.hot-desk", + enabled: true, + label: "Birouri Partajate", + description: "Rezervare birouri în camera partajată", + category: "module", + overridable: true, + }, // System flags { diff --git a/src/config/modules.ts b/src/config/modules.ts index 697bae2..9ec3a4c 100644 --- a/src/config/modules.ts +++ b/src/config/modules.ts @@ -1,36 +1,38 @@ -import type { ModuleConfig } from '@/core/module-registry/types'; -import { registerModules } from '@/core/module-registry'; +import type { ModuleConfig } from "@/core/module-registry/types"; +import { registerModules } from "@/core/module-registry"; -import { registraturaConfig } from '@/modules/registratura/config'; -import { emailSignatureConfig } from '@/modules/email-signature/config'; -import { wordXmlConfig } from '@/modules/word-xml/config'; -import { promptGeneratorConfig } from '@/modules/prompt-generator/config'; -import { digitalSignaturesConfig } from '@/modules/digital-signatures/config'; -import { passwordVaultConfig } from '@/modules/password-vault/config'; -import { itInventoryConfig } from '@/modules/it-inventory/config'; -import { addressBookConfig } from '@/modules/address-book/config'; -import { wordTemplatesConfig } from '@/modules/word-templates/config'; -import { tagManagerConfig } from '@/modules/tag-manager/config'; -import { miniUtilitiesConfig } from '@/modules/mini-utilities/config'; -import { aiChatConfig } from '@/modules/ai-chat/config'; +import { registraturaConfig } from "@/modules/registratura/config"; +import { emailSignatureConfig } from "@/modules/email-signature/config"; +import { wordXmlConfig } from "@/modules/word-xml/config"; +import { promptGeneratorConfig } from "@/modules/prompt-generator/config"; +import { digitalSignaturesConfig } from "@/modules/digital-signatures/config"; +import { passwordVaultConfig } from "@/modules/password-vault/config"; +import { itInventoryConfig } from "@/modules/it-inventory/config"; +import { addressBookConfig } from "@/modules/address-book/config"; +import { wordTemplatesConfig } from "@/modules/word-templates/config"; +import { tagManagerConfig } from "@/modules/tag-manager/config"; +import { miniUtilitiesConfig } from "@/modules/mini-utilities/config"; +import { aiChatConfig } from "@/modules/ai-chat/config"; +import { hotDeskConfig } from "@/modules/hot-desk/config"; /** * Toate configurările modulelor ArchiTools, ordonate după navOrder. * Dashboard-ul nu este inclus deoarece este pagina principală, nu un modul standard. */ export const MODULE_CONFIGS: ModuleConfig[] = [ - registraturaConfig, // navOrder: 10 | operations - passwordVaultConfig, // navOrder: 11 | operations - emailSignatureConfig, // navOrder: 20 | generators - wordXmlConfig, // navOrder: 21 | generators - wordTemplatesConfig, // navOrder: 22 | generators - digitalSignaturesConfig, // navOrder: 30 | management - itInventoryConfig, // navOrder: 31 | management - addressBookConfig, // navOrder: 32 | management - tagManagerConfig, // navOrder: 40 | tools - miniUtilitiesConfig, // navOrder: 41 | tools - promptGeneratorConfig, // navOrder: 50 | ai - aiChatConfig, // navOrder: 51 | ai + registraturaConfig, // navOrder: 10 | operations + passwordVaultConfig, // navOrder: 11 | operations + emailSignatureConfig, // navOrder: 20 | generators + wordXmlConfig, // navOrder: 21 | generators + wordTemplatesConfig, // navOrder: 22 | generators + digitalSignaturesConfig, // navOrder: 30 | management + itInventoryConfig, // navOrder: 31 | management + addressBookConfig, // navOrder: 32 | management + hotDeskConfig, // navOrder: 33 | management + tagManagerConfig, // navOrder: 40 | tools + miniUtilitiesConfig, // navOrder: 41 | tools + promptGeneratorConfig, // navOrder: 50 | ai + aiChatConfig, // navOrder: 51 | ai ]; // Înregistrare automată a tuturor modulelor în registru diff --git a/src/core/i18n/locales/ro.ts b/src/core/i18n/locales/ro.ts index 0fce02e..b9c7a2b 100644 --- a/src/core/i18n/locales/ro.ts +++ b/src/core/i18n/locales/ro.ts @@ -1,109 +1,113 @@ -import type { Labels } from '../types'; +import type { Labels } from "../types"; export const ro: Labels = { common: { - save: 'Salvează', - cancel: 'Anulează', - delete: 'Șterge', - edit: 'Editează', - create: 'Creează', - search: 'Caută', - filter: 'Filtrează', - export: 'Exportă', - import: 'Importă', - copy: 'Copiază', - close: 'Închide', - confirm: 'Confirmă', - back: 'Înapoi', - next: 'Următorul', - loading: 'Se încarcă...', - noResults: 'Niciun rezultat', - error: 'Eroare', - success: 'Succes', - actions: 'Acțiuni', - settings: 'Setări', - all: 'Toate', - yes: 'Da', - no: 'Nu', + save: "Salvează", + cancel: "Anulează", + delete: "Șterge", + edit: "Editează", + create: "Creează", + search: "Caută", + filter: "Filtrează", + export: "Exportă", + import: "Importă", + copy: "Copiază", + close: "Închide", + confirm: "Confirmă", + back: "Înapoi", + next: "Următorul", + loading: "Se încarcă...", + noResults: "Niciun rezultat", + error: "Eroare", + success: "Succes", + actions: "Acțiuni", + settings: "Setări", + all: "Toate", + yes: "Da", + no: "Nu", }, nav: { - dashboard: 'Panou principal', - operations: 'Operațiuni', - generators: 'Generatoare', - management: 'Management', - tools: 'Instrumente', - ai: 'AI & Automatizări', - externalTools: 'Instrumente externe', + dashboard: "Panou principal", + operations: "Operațiuni", + generators: "Generatoare", + management: "Management", + tools: "Instrumente", + ai: "AI & Automatizări", + externalTools: "Instrumente externe", }, dashboard: { - title: 'Panou principal', - welcome: 'Bine ai venit în ArchiTools', - subtitle: 'Platforma internă de instrumente pentru birou', - quickActions: 'Acțiuni rapide', - recentActivity: 'Activitate recentă', - modules: 'Module', - infrastructure: 'Infrastructură', + title: "Panou principal", + welcome: "Bine ai venit în ArchiTools", + subtitle: "Platforma internă de instrumente pentru birou", + quickActions: "Acțiuni rapide", + recentActivity: "Activitate recentă", + modules: "Module", + infrastructure: "Infrastructură", }, registratura: { - title: 'Registratură', - description: 'Registru de corespondență multi-firmă', - newEntry: 'Înregistrare nouă', - entries: 'Înregistrări', - incoming: 'Intrare', - outgoing: 'Ieșire', - internal: 'Intern', + title: "Registratură", + description: "Registru de corespondență multi-firmă", + newEntry: "Înregistrare nouă", + entries: "Înregistrări", + incoming: "Intrare", + outgoing: "Ieșire", + internal: "Intern", }, - 'email-signature': { - title: 'Generator Semnătură Email', - description: 'Configurator semnătură email pentru companii', - preview: 'Previzualizare', - downloadHtml: 'Descarcă HTML', + "email-signature": { + title: "Generator Semnătură Email", + description: "Configurator semnătură email pentru companii", + preview: "Previzualizare", + downloadHtml: "Descarcă HTML", }, - 'word-xml': { - title: 'Generator XML Word', - description: 'Generator Custom XML Parts pentru Word', - generate: 'Generează XML', - downloadXml: 'Descarcă XML', - downloadZip: 'Descarcă ZIP', + "word-xml": { + title: "Generator XML Word", + description: "Generator Custom XML Parts pentru Word", + generate: "Generează XML", + downloadXml: "Descarcă XML", + downloadZip: "Descarcă ZIP", }, - 'prompt-generator': { - title: 'Generator Prompturi', - description: 'Constructor de prompturi structurate pentru AI', - templates: 'Șabloane', - compose: 'Compune', - history: 'Istoric', - preview: 'Previzualizare', + "prompt-generator": { + title: "Generator Prompturi", + description: "Constructor de prompturi structurate pentru AI", + templates: "Șabloane", + compose: "Compune", + history: "Istoric", + preview: "Previzualizare", }, - 'digital-signatures': { - title: 'Semnături și Ștampile', - description: 'Bibliotecă semnături digitale și ștampile scanate', + "digital-signatures": { + title: "Semnături și Ștampile", + description: "Bibliotecă semnături digitale și ștampile scanate", }, - 'password-vault': { - title: 'Seif Parole', - description: 'Depozit intern de credențiale partajate', + "password-vault": { + title: "Seif Parole", + description: "Depozit intern de credențiale partajate", }, - 'it-inventory': { - title: 'Inventar IT', - description: 'Evidența echipamentelor și dispozitivelor', + "it-inventory": { + title: "Inventar IT", + description: "Evidența echipamentelor și dispozitivelor", }, - 'address-book': { - title: 'Contacte', - description: 'Clienți, furnizori, instituții', + "address-book": { + title: "Contacte", + description: "Clienți, furnizori, instituții", }, - 'word-templates': { - title: 'Șabloane Word', - description: 'Bibliotecă contracte, oferte, rapoarte', + "word-templates": { + title: "Șabloane Word", + description: "Bibliotecă contracte, oferte, rapoarte", }, - 'tag-manager': { - title: 'Manager Etichete', - description: 'Administrare etichete proiecte și categorii', + "tag-manager": { + title: "Manager Etichete", + description: "Administrare etichete proiecte și categorii", }, - 'mini-utilities': { - title: 'Utilitare', - description: 'Calculatoare tehnice și instrumente text', + "mini-utilities": { + title: "Utilitare", + description: "Calculatoare tehnice și instrumente text", }, - 'ai-chat': { - title: 'Chat AI', - description: 'Interfață asistent AI', + "ai-chat": { + title: "Chat AI", + description: "Interfață asistent AI", + }, + "hot-desk": { + title: "Birouri Partajate", + description: "Rezervare birouri în camera partajată", }, }; diff --git a/src/modules/address-book/components/address-book-module.tsx b/src/modules/address-book/components/address-book-module.tsx index 2a6bf3c..8b377f9 100644 --- a/src/modules/address-book/components/address-book-module.tsx +++ b/src/modules/address-book/components/address-book-module.tsx @@ -1,51 +1,89 @@ -'use client'; +"use client"; -import { useState } from 'react'; +import { useState } from "react"; import { - Plus, Pencil, Trash2, Search, Mail, Phone, MapPin, - Globe, Building2, UserPlus, X, Download, FileText, -} from 'lucide-react'; -import { Button } from '@/shared/components/ui/button'; -import { Input } from '@/shared/components/ui/input'; -import { Label } from '@/shared/components/ui/label'; -import { Textarea } from '@/shared/components/ui/textarea'; -import { Badge } from '@/shared/components/ui/badge'; -import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card'; + Plus, + Pencil, + Trash2, + Search, + Mail, + Phone, + MapPin, + Globe, + Building2, + UserPlus, + X, + Download, + FileText, +} from "lucide-react"; +import { Button } from "@/shared/components/ui/button"; +import { Input } from "@/shared/components/ui/input"; +import { Label } from "@/shared/components/ui/label"; +import { Textarea } from "@/shared/components/ui/textarea"; +import { Badge } from "@/shared/components/ui/badge"; import { - Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from '@/shared/components/ui/select'; + Card, + CardContent, + CardHeader, + CardTitle, +} from "@/shared/components/ui/card"; import { - Dialog, DialogContent, DialogHeader, DialogTitle, -} from '@/shared/components/ui/dialog'; -import type { AddressContact, ContactType, ContactPerson } from '../types'; -import { useContacts } from '../hooks/use-contacts'; -import { useTags } from '@/core/tagging'; -import { downloadVCard } from '../services/vcard-export'; -import { useRegistry } from '@/modules/registratura/hooks/use-registry'; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/shared/components/ui/select"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; +import type { AddressContact, ContactType, ContactPerson } from "../types"; +import { useContacts } from "../hooks/use-contacts"; +import { useTags } from "@/core/tagging"; +import { downloadVCard } from "../services/vcard-export"; +import { useRegistry } from "@/modules/registratura/hooks/use-registry"; const TYPE_LABELS: Record = { - client: 'Client', - supplier: 'Furnizor', - institution: 'Instituție', - collaborator: 'Colaborator', - internal: 'Intern', + client: "Client", + supplier: "Furnizor", + institution: "Instituție", + collaborator: "Colaborator", + internal: "Intern", }; -type ViewMode = 'list' | 'add' | 'edit'; +type ViewMode = "list" | "add" | "edit"; export function AddressBookModule() { - const { contacts, allContacts, loading, filters, updateFilter, addContact, updateContact, removeContact } = useContacts(); - const [viewMode, setViewMode] = useState('list'); - const [editingContact, setEditingContact] = useState(null); - const [viewingContact, setViewingContact] = useState(null); + const { + contacts, + allContacts, + loading, + filters, + updateFilter, + addContact, + updateContact, + removeContact, + } = useContacts(); + const [viewMode, setViewMode] = useState("list"); + const [editingContact, setEditingContact] = useState( + null, + ); + const [viewingContact, setViewingContact] = useState( + null, + ); - const handleSubmit = async (data: Omit) => { - if (viewMode === 'edit' && editingContact) { + const handleSubmit = async ( + data: Omit, + ) => { + if (viewMode === "edit" && editingContact) { await updateContact(editingContact.id, data); } else { await addContact(data); } - setViewMode('list'); + setViewMode("list"); setEditingContact(null); }; @@ -53,47 +91,79 @@ export function AddressBookModule() {
{/* Stats */}
-

Total

{allContacts.length}

+ + +

Total

+

{allContacts.length}

+
+
{(Object.keys(TYPE_LABELS) as ContactType[]).slice(0, 4).map((type) => ( - -

{TYPE_LABELS[type]}

-

{allContacts.filter((c) => c.type === type).length}

-
+ + +

+ {TYPE_LABELS[type]} +

+

+ {allContacts.filter((c) => c.type === type).length} +

+
+
))}
- {viewMode === 'list' && ( + {viewMode === "list" && ( <>
- updateFilter('search', e.target.value)} className="pl-9" /> + updateFilter("search", e.target.value)} + className="pl-9" + />
- + updateFilter("type", v as ContactType | "all") + } + > + + + Toate tipurile {(Object.keys(TYPE_LABELS) as ContactType[]).map((t) => ( - {TYPE_LABELS[t]} + + {TYPE_LABELS[t]} + ))} -
{loading ? ( -

Se încarcă...

+

+ Se încarcă... +

) : contacts.length === 0 ? ( -

Niciun contact găsit.

+

+ Niciun contact găsit. +

) : (
{contacts.map((contact) => ( { setEditingContact(contact); setViewMode('edit'); }} + onEdit={() => { + setEditingContact(contact); + setViewMode("edit"); + }} onDelete={() => removeContact(contact.id)} onViewDetail={() => setViewingContact(contact)} /> @@ -103,14 +173,21 @@ export function AddressBookModule() { )} - {(viewMode === 'add' || viewMode === 'edit') && ( + {(viewMode === "add" || viewMode === "edit") && ( - {viewMode === 'edit' ? 'Editare contact' : 'Contact nou'} + + + {viewMode === "edit" ? "Editare contact" : "Contact nou"} + + { setViewMode('list'); setEditingContact(null); }} + onCancel={() => { + setViewMode("list"); + setEditingContact(null); + }} /> @@ -120,7 +197,11 @@ export function AddressBookModule() { setViewingContact(null)} - onEdit={(c) => { setViewingContact(null); setEditingContact(c); setViewMode('edit'); }} + onEdit={(c) => { + setViewingContact(null); + setEditingContact(c); + setViewMode("edit"); + }} />
); @@ -128,7 +209,12 @@ export function AddressBookModule() { // ── Contact Card ── -function ContactCard({ contact, onEdit, onDelete, onViewDetail }: { +function ContactCard({ + contact, + onEdit, + onDelete, + onViewDetail, +}: { contact: AddressContact; onEdit: () => void; onDelete: () => void; @@ -138,16 +224,38 @@ function ContactCard({ contact, onEdit, onDelete, onViewDetail }: {
- - - -
@@ -155,44 +263,60 @@ function ContactCard({ contact, onEdit, onDelete, onViewDetail }: {

{contact.name}

- {contact.company &&

{contact.company}

} - {TYPE_LABELS[contact.type]} + {contact.company && ( +

+ {contact.company} +

+ )} + + {TYPE_LABELS[contact.type]} + {contact.department && ( - {contact.department} + + {contact.department} + )}
{contact.role && ( -

{contact.role}

+

+ {contact.role} +

)}
{contact.email && (
- {contact.email} + + {contact.email}
)} {contact.email2 && (
- {contact.email2} + + {contact.email2}
)} {contact.phone && (
- {contact.phone} + + {contact.phone}
)} {contact.phone2 && (
- {contact.phone2} + + {contact.phone2}
)} {contact.address && (
- {contact.address} + + {contact.address}
)} {contact.website && (
- {contact.website} + + {contact.website}
)} {(contact.contactPersons ?? []).length > 0 && ( @@ -202,7 +326,8 @@ function ContactCard({ contact, onEdit, onDelete, onViewDetail }: {

{contact.contactPersons.slice(0, 2).map((cp, i) => (

- {cp.name}{cp.role ? ` — ${cp.role}` : ''} + {cp.name} + {cp.role ? ` — ${cp.role}` : ""}

))} {contact.contactPersons.length > 2 && ( @@ -221,11 +346,15 @@ function ContactCard({ contact, onEdit, onDelete, onViewDetail }: { // ── Contact Detail Dialog (with Registratura reverse lookup) ── const DIRECTION_LABELS: Record = { - intrat: 'Intrat', - iesit: 'Ieșit', + intrat: "Intrat", + iesit: "Ieșit", }; -function ContactDetailDialog({ contact, onClose, onEdit }: { +function ContactDetailDialog({ + contact, + onClose, + onEdit, +}: { contact: AddressContact | null; onClose: () => void; onEdit: (c: AddressContact) => void; @@ -236,11 +365,17 @@ function ContactDetailDialog({ contact, onClose, onEdit }: { // Find registratura entries linked to this contact (search all, ignoring active filters) const linkedEntries = allEntries.filter( - (e) => e.senderContactId === contact.id || e.recipientContactId === contact.id + (e) => + e.senderContactId === contact.id || e.recipientContactId === contact.id, ); return ( - { if (!open) onClose(); }}> + { + if (!open) onClose(); + }} + > @@ -255,41 +390,68 @@ function ContactDetailDialog({ contact, onClose, onEdit }: { {contact.company && (
- {contact.company}{contact.department ? ` — ${contact.department}` : ''} + + {contact.company} + {contact.department ? ` — ${contact.department}` : ""} +
)} - {contact.role &&

{contact.role}

} + {contact.role && ( +

+ {contact.role} +

+ )} {contact.email && ( )} {contact.email2 && ( )} {contact.phone && (
- {contact.phone} + + {contact.phone}
)} {contact.phone2 && (
- {contact.phone2} + + {contact.phone2}
)} {contact.address && (
- {contact.address} + + {contact.address}
)} {contact.website && ( )}
@@ -297,14 +459,34 @@ function ContactDetailDialog({ contact, onClose, onEdit }: { {/* Contact persons */} {contact.contactPersons && contact.contactPersons.length > 0 && (
-

Persoane de contact

+

+ Persoane de contact +

{contact.contactPersons.map((cp, i) => ( -
+
{cp.name} - {cp.role && {cp.role}} - {cp.email && {cp.email}} - {cp.phone && {cp.phone}} + {cp.role && ( + + {cp.role} + + )} + {cp.email && ( + + {cp.email} + + )} + {cp.phone && ( + + {cp.phone} + + )}
))}
@@ -313,8 +495,12 @@ function ContactDetailDialog({ contact, onClose, onEdit }: { {contact.notes && (
-

Note

-

{contact.notes}

+

+ Note +

+

+ {contact.notes} +

)} @@ -324,22 +510,35 @@ function ContactDetailDialog({ contact, onClose, onEdit }: { Registratură ({linkedEntries.length})

{linkedEntries.length === 0 ? ( -

Nicio înregistrare în registratură pentru acest contact.

+

+ Nicio înregistrare în registratură pentru acest contact. +

) : (
{linkedEntries.map((entry) => ( -
+
{DIRECTION_LABELS[entry.direction] ?? entry.direction} - {entry.number} - {entry.subject} - {entry.date} + + {entry.number} + + + {entry.subject} + + + {entry.date} + - {entry.status === 'deschis' ? 'Deschis' : 'Închis'} + {entry.status === "deschis" ? "Deschis" : "Închis"}
))} @@ -349,7 +548,11 @@ function ContactDetailDialog({ contact, onClose, onEdit }: { {/* Actions */}
- ))}
@@ -487,19 +802,61 @@ function ContactForm({ initial, onSubmit, onCancel }: {
-
{contactPersons.length > 0 && (
{contactPersons.map((cp, i) => ( -
- updateContactPerson(i, 'name', e.target.value)} className="min-w-[150px] flex-1 text-sm" /> - updateContactPerson(i, 'role', e.target.value)} className="w-[140px] text-sm" /> - updateContactPerson(i, 'email', e.target.value)} className="w-[180px] text-sm" /> - updateContactPerson(i, 'phone', e.target.value)} className="w-[140px] text-sm" /> -
@@ -509,11 +866,21 @@ function ContactForm({ initial, onSubmit, onCancel }: {
{/* Notes */} -