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:
159
src/modules/mini-utilities/components/mini-utilities-module.tsx
Normal file
159
src/modules/mini-utilities/components/mini-utilities-module.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Copy, Check, Hash, Type, Percent, Ruler } 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 { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/components/ui/tabs';
|
||||
|
||||
function CopyButton({ text }: { text: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch { /* silent */ }
|
||||
};
|
||||
return (
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={handleCopy} disabled={!text}>
|
||||
{copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function TextCaseConverter() {
|
||||
const [input, setInput] = useState('');
|
||||
const upper = input.toUpperCase();
|
||||
const lower = input.toLowerCase();
|
||||
const title = input.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
const sentence = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div><Label>Text sursă</Label><Textarea value={input} onChange={(e) => setInput(e.target.value)} rows={3} className="mt-1" placeholder="Introdu text..." /></div>
|
||||
{[
|
||||
{ label: 'UPPERCASE', value: upper },
|
||||
{ label: 'lowercase', value: lower },
|
||||
{ label: 'Title Case', value: title },
|
||||
{ label: 'Sentence case', value: sentence },
|
||||
].map(({ label, value }) => (
|
||||
<div key={label} className="flex items-center gap-2">
|
||||
<code className="flex-1 truncate rounded border bg-muted/30 px-2 py-1 text-xs">{value || '—'}</code>
|
||||
<span className="w-24 text-xs text-muted-foreground">{label}</span>
|
||||
<CopyButton text={value} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CharacterCounter() {
|
||||
const [input, setInput] = useState('');
|
||||
const chars = input.length;
|
||||
const charsNoSpaces = input.replace(/\s/g, '').length;
|
||||
const words = input.trim() ? input.trim().split(/\s+/).length : 0;
|
||||
const lines = input ? input.split('\n').length : 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div><Label>Text</Label><Textarea value={input} onChange={(e) => setInput(e.target.value)} rows={5} className="mt-1" placeholder="Introdu text..." /></div>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<Card><CardContent className="p-3"><p className="text-xs text-muted-foreground">Caractere</p><p className="text-xl font-bold">{chars}</p></CardContent></Card>
|
||||
<Card><CardContent className="p-3"><p className="text-xs text-muted-foreground">Fără spații</p><p className="text-xl font-bold">{charsNoSpaces}</p></CardContent></Card>
|
||||
<Card><CardContent className="p-3"><p className="text-xs text-muted-foreground">Cuvinte</p><p className="text-xl font-bold">{words}</p></CardContent></Card>
|
||||
<Card><CardContent className="p-3"><p className="text-xs text-muted-foreground">Linii</p><p className="text-xl font-bold">{lines}</p></CardContent></Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PercentageCalculator() {
|
||||
const [value, setValue] = useState('');
|
||||
const [total, setTotal] = useState('');
|
||||
const [percent, setPercent] = useState('');
|
||||
|
||||
const v = parseFloat(value);
|
||||
const t = parseFloat(total);
|
||||
const p = parseFloat(percent);
|
||||
|
||||
const pctOfTotal = !isNaN(v) && !isNaN(t) && t !== 0 ? ((v / t) * 100).toFixed(2) : '—';
|
||||
const valFromPct = !isNaN(p) && !isNaN(t) ? ((p / 100) * t).toFixed(2) : '—';
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
<div><Label>Valoare</Label><Input type="number" value={value} onChange={(e) => setValue(e.target.value)} className="mt-1" /></div>
|
||||
<div><Label>Total</Label><Input type="number" value={total} onChange={(e) => setTotal(e.target.value)} className="mt-1" /></div>
|
||||
<div><Label>Procent</Label><Input type="number" value={percent} onChange={(e) => setPercent(e.target.value)} className="mt-1" /></div>
|
||||
</div>
|
||||
<div className="space-y-2 rounded-md border bg-muted/30 p-3 text-sm">
|
||||
<p><strong>{value || '?'}</strong> din <strong>{total || '?'}</strong> = <strong>{pctOfTotal}%</strong></p>
|
||||
<p><strong>{percent || '?'}%</strong> din <strong>{total || '?'}</strong> = <strong>{valFromPct}</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AreaConverter() {
|
||||
const [mp, setMp] = useState('');
|
||||
const v = parseFloat(mp);
|
||||
|
||||
const conversions = !isNaN(v) ? [
|
||||
{ label: 'mp (m²)', value: v.toFixed(2) },
|
||||
{ label: 'ari (100 m²)', value: (v / 100).toFixed(4) },
|
||||
{ label: 'hectare (10.000 m²)', value: (v / 10000).toFixed(6) },
|
||||
{ label: 'km²', value: (v / 1000000).toFixed(8) },
|
||||
{ label: 'sq ft', value: (v * 10.7639).toFixed(2) },
|
||||
] : [];
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div><Label>Suprafață (m²)</Label><Input type="number" value={mp} onChange={(e) => setMp(e.target.value)} className="mt-1" placeholder="Introdu suprafața..." /></div>
|
||||
{conversions.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
{conversions.map(({ label, value: val }) => (
|
||||
<div key={label} className="flex items-center gap-2">
|
||||
<code className="flex-1 rounded border bg-muted/30 px-2 py-1 text-xs">{val}</code>
|
||||
<span className="w-36 text-xs text-muted-foreground">{label}</span>
|
||||
<CopyButton text={val} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MiniUtilitiesModule() {
|
||||
return (
|
||||
<Tabs defaultValue="text-case" className="space-y-4">
|
||||
<TabsList className="flex-wrap">
|
||||
<TabsTrigger value="text-case"><Type className="mr-1 h-3.5 w-3.5" /> Transformare text</TabsTrigger>
|
||||
<TabsTrigger value="char-count"><Hash className="mr-1 h-3.5 w-3.5" /> Numărare caractere</TabsTrigger>
|
||||
<TabsTrigger value="percentage"><Percent className="mr-1 h-3.5 w-3.5" /> Procente</TabsTrigger>
|
||||
<TabsTrigger value="area"><Ruler className="mr-1 h-3.5 w-3.5" /> Convertor suprafețe</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="text-case">
|
||||
<Card><CardHeader><CardTitle className="text-base">Transformare text</CardTitle></CardHeader>
|
||||
<CardContent><TextCaseConverter /></CardContent></Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="char-count">
|
||||
<Card><CardHeader><CardTitle className="text-base">Numărare caractere</CardTitle></CardHeader>
|
||||
<CardContent><CharacterCounter /></CardContent></Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="percentage">
|
||||
<Card><CardHeader><CardTitle className="text-base">Calculator procente</CardTitle></CardHeader>
|
||||
<CardContent><PercentageCalculator /></CardContent></Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="area">
|
||||
<Card><CardHeader><CardTitle className="text-base">Convertor suprafețe</CardTitle></CardHeader>
|
||||
<CardContent><AreaConverter /></CardContent></Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user