Initial commit: ArchiTools modular dashboard platform

Complete Next.js 16 application with 13 fully implemented modules:
Email Signature, Word XML Generator, Registratura, Dashboard,
Tag Manager, IT Inventory, Address Book, Password Vault,
Mini Utilities, Prompt Generator, Digital Signatures,
Word Templates, and AI Chat.

Includes core platform systems (module registry, feature flags,
storage abstraction, i18n, theming, auth stub, tagging),
16 technical documentation files, Docker deployment config,
and legacy HTML tool reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Marius Tarau
2026-02-17 12:50:25 +02:00
commit 4c46e8bcdd
189 changed files with 33780 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
'use client';
import { useState } from 'react';
import { Plus, Trash2, Tag as TagIcon } 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 { Badge } from '@/shared/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
import { useTags } from '@/core/tagging';
import type { TagCategory, TagScope } from '@/core/tagging/types';
import { cn } from '@/shared/lib/utils';
const CATEGORY_LABELS: Record<TagCategory, string> = {
project: 'Proiect',
phase: 'Fază',
activity: 'Activitate',
'document-type': 'Tip document',
company: 'Companie',
priority: 'Prioritate',
status: 'Status',
custom: 'Personalizat',
};
const SCOPE_LABELS: Record<TagScope, string> = {
global: 'Global',
module: 'Modul',
company: 'Companie',
};
const TAG_COLORS = [
'#ef4444', '#f97316', '#f59e0b', '#84cc16',
'#22c55e', '#06b6d4', '#3b82f6', '#8b5cf6',
'#ec4899', '#64748b',
];
export function TagManagerModule() {
const { tags, loading, createTag, deleteTag } = useTags();
const [newLabel, setNewLabel] = useState('');
const [newCategory, setNewCategory] = useState<TagCategory>('custom');
const [newScope, setNewScope] = useState<TagScope>('global');
const [newColor, setNewColor] = useState(TAG_COLORS[5]);
const [filterCategory, setFilterCategory] = useState<TagCategory | 'all'>('all');
const handleCreate = async () => {
if (!newLabel.trim()) return;
await createTag({
label: newLabel.trim(),
category: newCategory,
scope: newScope,
color: newColor,
});
setNewLabel('');
};
const filteredTags = filterCategory === 'all'
? tags
: tags.filter((t) => t.category === filterCategory);
const groupedByCategory = filteredTags.reduce<Record<string, typeof tags>>((acc, tag) => {
const key = tag.category;
if (!acc[key]) acc[key] = [];
acc[key].push(tag);
return acc;
}, {});
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<Card><CardContent className="p-4">
<p className="text-xs text-muted-foreground">Total etichete</p>
<p className="text-2xl font-bold">{tags.length}</p>
</CardContent></Card>
<Card><CardContent className="p-4">
<p className="text-xs text-muted-foreground">Categorii folosite</p>
<p className="text-2xl font-bold">{new Set(tags.map((t) => t.category)).size}</p>
</CardContent></Card>
<Card><CardContent className="p-4">
<p className="text-xs text-muted-foreground">Globale</p>
<p className="text-2xl font-bold">{tags.filter((t) => t.scope === 'global').length}</p>
</CardContent></Card>
<Card><CardContent className="p-4">
<p className="text-xs text-muted-foreground">Personalizate</p>
<p className="text-2xl font-bold">{tags.filter((t) => t.category === 'custom').length}</p>
</CardContent></Card>
</div>
{/* Create new tag */}
<Card>
<CardHeader><CardTitle className="text-base">Etichetă nouă</CardTitle></CardHeader>
<CardContent>
<div className="flex flex-wrap items-end gap-3">
<div className="min-w-[200px] flex-1">
<Label>Nume</Label>
<Input
value={newLabel}
onChange={(e) => setNewLabel(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
placeholder="Numele etichetei..."
className="mt-1"
/>
</div>
<div className="w-[160px]">
<Label>Categorie</Label>
<Select value={newCategory} onValueChange={(v) => setNewCategory(v as TagCategory)}>
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
{(Object.keys(CATEGORY_LABELS) as TagCategory[]).map((cat) => (
<SelectItem key={cat} value={cat}>{CATEGORY_LABELS[cat]}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-[130px]">
<Label>Vizibilitate</Label>
<Select value={newScope} onValueChange={(v) => setNewScope(v as TagScope)}>
<SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
{(Object.keys(SCOPE_LABELS) as TagScope[]).map((s) => (
<SelectItem key={s} value={s}>{SCOPE_LABELS[s]}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label className="mb-1.5 block">Culoare</Label>
<div className="flex gap-1">
{TAG_COLORS.map((color) => (
<button
key={color}
type="button"
onClick={() => setNewColor(color)}
className={cn(
'h-7 w-7 rounded-full border-2 transition-all',
newColor === color ? 'border-primary scale-110' : 'border-transparent hover:scale-105'
)}
style={{ backgroundColor: color }}
/>
))}
</div>
</div>
<Button onClick={handleCreate} disabled={!newLabel.trim()}>
<Plus className="mr-1 h-4 w-4" /> Adaugă
</Button>
</div>
</CardContent>
</Card>
{/* Filter */}
<div className="flex items-center gap-3">
<Label>Filtrează:</Label>
<Select value={filterCategory} onValueChange={(v) => setFilterCategory(v as TagCategory | 'all')}>
<SelectTrigger className="w-[180px]"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="all">Toate categoriile</SelectItem>
{(Object.keys(CATEGORY_LABELS) as TagCategory[]).map((cat) => (
<SelectItem key={cat} value={cat}>{CATEGORY_LABELS[cat]}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Tag list by category */}
{loading ? (
<p className="py-8 text-center text-sm text-muted-foreground">Se încarcă...</p>
) : Object.keys(groupedByCategory).length === 0 ? (
<p className="py-8 text-center text-sm text-muted-foreground">Nicio etichetă găsită. Creează prima etichetă.</p>
) : (
<div className="space-y-4">
{Object.entries(groupedByCategory).map(([category, catTags]) => (
<Card key={category}>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-sm">
<TagIcon className="h-4 w-4" />
{CATEGORY_LABELS[category as TagCategory] ?? category}
<Badge variant="secondary" className="ml-1">{catTags.length}</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{catTags.map((tag) => (
<div
key={tag.id}
className="group flex items-center gap-1.5 rounded-full border py-1 pl-3 pr-1.5 text-sm"
>
{tag.color && (
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: tag.color }} />
)}
<span>{tag.label}</span>
<Badge variant="outline" className="text-[10px] px-1">{SCOPE_LABELS[tag.scope]}</Badge>
<button
type="button"
onClick={() => deleteTag(tag.id)}
className="ml-0.5 rounded-full p-0.5 opacity-0 transition-opacity hover:bg-destructive/10 group-hover:opacity-100"
>
<Trash2 className="h-3 w-3 text-destructive" />
</button>
</div>
))}
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,17 @@
import type { ModuleConfig } from '@/core/module-registry/types';
export const tagManagerConfig: ModuleConfig = {
id: 'tag-manager',
name: 'Manager Etichete',
description: 'Administrare centralizată a etichetelor și categoriilor din platformă',
icon: 'tags',
route: '/tag-manager',
category: 'tools',
featureFlag: 'module.tag-manager',
visibility: 'all',
version: '0.1.0',
dependencies: [],
storageNamespace: 'tag-manager',
navOrder: 40,
tags: ['etichete', 'categorii', 'organizare'],
};

View File

@@ -0,0 +1,3 @@
export { tagManagerConfig } from './config';
export { TagManagerModule } from './components/tag-manager-module';
export type { Tag, TagCategory, TagScope } from './types';

View File

@@ -0,0 +1 @@
export type { Tag, TagCategory, TagScope } from '@/core/tagging/types';