|
|
|
@@ -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>
|
|
|
|
|
);
|
|
|
|
|
}
|