armario 1

This commit is contained in:
2026-04-15 10:39:24 +01:00
parent 2d015444d3
commit bd56d8e01a
2 changed files with 171 additions and 15 deletions

View File

@@ -38,6 +38,9 @@ export default function App() {
const [authMode, setAuthMode] = useState('login');
const [authError, setAuthError] = useState('');
const [categoryFilter, setCategoryFilter] = useState('Todos');
const [colorFilter, setColorFilter] = useState('');
const [ageFilter, setAgeFilter] = useState('any');
const [showClosetFilters, setShowClosetFilters] = useState(false);
// Estado para criação de Looks
const [selectedForLook, setSelectedForLook] = useState([]);
@@ -110,14 +113,31 @@ export default function App() {
const laundryClothes = useMemo(() => clothes.filter(c => c.status === 'laundry'), [clothes]);
const trashClothes = useMemo(() => clothes.filter(c => c.status === 'trash'), [clothes]);
const availableColors = useMemo(() => {
const colors = new Set(activeClothes.map(c => c.color).filter(Boolean));
return Array.from(colors);
}, [activeClothes]);
const filteredClothes = useMemo(() => {
return activeClothes.filter(c => {
const matchesSearch = (c.name || "").toLowerCase().includes(searchTerm.toLowerCase()) ||
(c.color || "").toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = categoryFilter === 'Todos' || c.category === categoryFilter;
return matchesSearch && matchesCategory;
const matchesCategory = categoryFilter === 'Todos' || categoryFilter === t('all') || c.category === categoryFilter;
const matchesColor = !colorFilter || c.color === colorFilter;
let matchesAge = true;
if (ageFilter !== 'any') {
const ageInMs = new Date().getTime() - (c.createdAt || new Date().getTime());
const days = ageInMs / (1000 * 60 * 60 * 24);
if (ageFilter === 'month') matchesAge = days <= 30;
else if (ageFilter === '6months') matchesAge = days <= 180;
else if (ageFilter === '1year') matchesAge = days <= 365;
else if (ageFilter === 'older') matchesAge = days > 365;
}
return matchesSearch && matchesCategory && matchesColor && matchesAge;
});
}, [activeClothes, searchTerm, categoryFilter]);
}, [activeClothes, searchTerm, categoryFilter, colorFilter, ageFilter, t]);
// Ações de Itens
const handleItemAction = async (action, item) => {
@@ -249,10 +269,18 @@ export default function App() {
const fd = new FormData(e.target);
try {
const profileDoc = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data');
const dobDay = fd.get('dobDay');
const dobMonth = fd.get('dobMonth');
const dobYear = fd.get('dobYear');
let dob = fd.get('dob') || '';
if (dobDay && dobMonth && dobYear) {
dob = `${dobYear}-${dobMonth}-${dobDay}`;
}
await setDoc(profileDoc, {
username: fd.get('username') || '',
fullName: fd.get('fullName') || '',
dob: fd.get('dob') || '',
dob: dob,
bio: fd.get('bio') || ''
}, { merge: true });
} catch (err) {
@@ -461,16 +489,16 @@ export default function App() {
/>
</div>
<div className="flex gap-3 overflow-x-auto pb-4 w-full xl:w-auto custom-scrollbar">
{[t('all'), t('tops'), t('bottoms'), t('footwear'), t('coats'), t('accessories')].map(cat => (
<div className="flex gap-3 w-full xl:w-auto">
<button
key={cat}
onClick={() => setCategoryFilter(cat)}
className={`px-8 py-4 rounded-2xl font-black text-xs uppercase tracking-widest transition-all ${categoryFilter === cat ? 'bg-primary-600 text-white shadow-xl shadow-primary-600/30' : (darkMode ? 'bg-gray-800 text-gray-400' : 'bg-white text-gray-500 shadow-sm border border-gray-100')}`}
onClick={() => setShowClosetFilters(true)}
className="flex items-center gap-3 px-8 py-4 bg-primary-600 text-white rounded-[2rem] font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 hover:scale-105 transition-all"
>
{cat}
<Filter size={18} /> {t('advancedFilters')}
{(colorFilter || ageFilter !== 'any' || (categoryFilter !== 'Todos' && categoryFilter !== t('all'))) && (
<span className="w-2 h-2 rounded-full bg-white animate-pulse"></span>
)}
</button>
))}
</div>
</div>
@@ -678,7 +706,23 @@ export default function App() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Input label={t('username')} name="username" defaultValue={userProfile?.username || ''} placeholder="Ex: amari" />
<Input label={t('fullName')} name="fullName" defaultValue={userProfile?.fullName || ''} placeholder="Ex: Amari Rodriguez" />
<Input label={`${t('dob')} ${t('optional')}`} name="dob" type="date" defaultValue={userProfile?.dob || ''} />
<div className="space-y-2">
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('dob')} {t('optional')}</label>
<div className="flex gap-2">
<select name="dobDay" defaultValue={userProfile?.dob?.split('-')[2] || ''} className={`flex-1 p-4 rounded-xl border-none outline-none focus:ring-2 focus:ring-primary-500 font-bold ${darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'}`}>
<option value="">DD</option>
{Array.from({length: 31}, (_, i) => String(i+1).padStart(2,'0')).map(d => <option key={d} value={d}>{d}</option>)}
</select>
<select name="dobMonth" defaultValue={userProfile?.dob?.split('-')[1] || ''} className={`flex-1 p-4 rounded-xl border-none outline-none focus:ring-2 focus:ring-primary-500 font-bold ${darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'}`}>
<option value="">MM</option>
{Array.from({length: 12}, (_, i) => String(i+1).padStart(2,'0')).map(m => <option key={m} value={m}>{m}</option>)}
</select>
<select name="dobYear" defaultValue={userProfile?.dob?.split('-')[0] || ''} className={`flex-[1.5] p-4 rounded-xl border-none outline-none focus:ring-2 focus:ring-primary-500 font-bold ${darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'}`}>
<option value="">YYYY</option>
{Array.from({length: 100}, (_, i) => new Date().getFullYear() - i).map(y => <option key={y} value={y}>{y}</option>)}
</select>
</div>
</div>
<Input label={`${t('bio')} ${t('optional')}`} name="bio" defaultValue={userProfile?.bio || ''} placeholder="..." />
</div>
<button disabled={savingProfile} type="submit" className="w-full py-4 bg-primary-600 text-white rounded-xl font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 disabled:opacity-50 hover:scale-[1.01] transition-all">
@@ -801,6 +845,73 @@ export default function App() {
</div>
</main>
{/* Modal de Filtros Avançados */}
{showClosetFilters && (
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowClosetFilters(false)}>
<Card className="w-full max-w-lg p-8 animate-in zoom-in-95 flex flex-col max-h-[90vh]" darkMode={darkMode} onClick={e => e.stopPropagation()}>
<div className="flex items-center justify-between mb-8">
<h3 className="text-2xl font-black text-inherit flex items-center gap-3"><Filter size={24} className="text-primary-600" /> {t('advancedFilters')}</h3>
<button onClick={() => setShowClosetFilters(false)} className="p-2 bg-gray-100 dark:bg-gray-800 rounded-full hover:scale-110 transition-all text-inherit"><X size={20} /></button>
</div>
<div className="flex-1 overflow-y-auto space-y-8 pr-2 custom-scrollbar">
<div className="space-y-4">
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('closet')}</label>
<div className="flex flex-wrap gap-2">
{[t('all'), t('tops'), t('bottoms'), t('footwear'), t('coats'), t('accessories')].map(cat => (
<button
key={cat}
onClick={() => setCategoryFilter(cat)}
className={`px-5 py-3 rounded-xl font-black text-[10px] uppercase tracking-widest transition-all ${categoryFilter === cat ? 'bg-primary-600 text-white shadow-xl shadow-primary-600/30' : (darkMode ? 'bg-gray-800 text-gray-400' : 'bg-gray-50 text-gray-500 shadow-sm border border-gray-100')} hover:scale-[1.02]`}
>
{cat}
</button>
))}
</div>
</div>
<div className="space-y-4">
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('filterByColor')}</label>
<select
value={colorFilter}
onChange={(e) => setColorFilter(e.target.value)}
className={`w-full p-4 rounded-2xl border-none outline-none focus:ring-2 focus:ring-primary-500 font-bold ${darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'}`}
>
<option value="">{t('all')}</option>
{availableColors.map(c => <option key={c} value={c}>{c}</option>)}
</select>
</div>
<div className="space-y-4">
<label className="text-[10px] font-black uppercase opacity-40 tracking-widest ml-1 text-inherit">{t('filterByAge')}</label>
<div className="grid grid-cols-2 gap-2">
{[
{ id: 'any', label: t('anyAge') },
{ id: 'month', label: t('lessThanMonth') },
{ id: '6months', label: t('lessThan6Months') },
{ id: '1year', label: t('lessThanYear') },
{ id: 'older', label: t('older') }
].map(ageOpt => (
<button
key={ageOpt.id}
onClick={() => setAgeFilter(ageOpt.id)}
className={`px-4 py-3 rounded-xl font-bold text-xs transition-all ${ageFilter === ageOpt.id ? 'bg-primary-600 text-white shadow-xl shadow-primary-600/30' : (darkMode ? 'bg-gray-800 text-gray-400' : 'bg-gray-50 text-gray-500')} ${ageOpt.id === 'any' ? 'col-span-2' : ''}`}
>
{ageOpt.label}
</button>
))}
</div>
</div>
</div>
<div className="pt-8 flex gap-4 border-t mt-8 border-gray-100 dark:border-gray-800">
<button onClick={() => { setCategoryFilter('Todos'); setColorFilter(''); setAgeFilter('any'); }} className="flex-1 py-4 font-black uppercase text-[10px] tracking-widest text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors">{t('clearAll')}</button>
<button onClick={() => setShowClosetFilters(false)} className="flex-1 py-4 bg-primary-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 hover:scale-105 transition-all">{t('applyFilters')}</button>
</div>
</Card>
</div>
)}
{/* Modal de Idioma */}
{showLangModal && (
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/60 backdrop-blur-sm p-6" onClick={() => setShowLangModal(false)}>

View File

@@ -93,6 +93,15 @@ export const translations = {
userTitle: "Utilizador",
themeColor: "Cor do Tema",
personalizeColor: "Personalizar a cor",
advancedFilters: "Filtros Avançados",
filterByColor: "Cor",
filterByAge: "Idade da Peça",
anyAge: "Qualquer Idade",
lessThanMonth: "Menos de 1 Mês",
lessThan6Months: "Menos de 6 Meses",
lessThanYear: "Menos de 1 Ano",
older: "Mais antigo",
applyFilters: "Aplicar Filtros",
profileInfo: "Informações da Conta",
username: "Nome de Utilizador",
fullName: "Nome Completo",
@@ -195,6 +204,15 @@ export const translations = {
userTitle: "User",
themeColor: "Theme Color",
personalizeColor: "Personalize the color",
advancedFilters: "Advanced Filters",
filterByColor: "Color",
filterByAge: "Item Age",
anyAge: "Any Age",
lessThanMonth: "Less than 1 Month",
lessThan6Months: "Less than 6 Months",
lessThanYear: "Less than 1 Year",
older: "Older",
applyFilters: "Apply Filters",
profileInfo: "Account Information",
username: "Username",
fullName: "Full Name",
@@ -297,6 +315,15 @@ export const translations = {
userTitle: "Usuario",
themeColor: "Color del Tema",
personalizeColor: "Personaliza el color",
advancedFilters: "Filtros Avanzados",
filterByColor: "Color",
filterByAge: "Edad de la Prenda",
anyAge: "Cualquier Edad",
lessThanMonth: "Menos de 1 Mes",
lessThan6Months: "Menos de 6 Meses",
lessThanYear: "Menos de 1 Año",
older: "Más Antiguo",
applyFilters: "Aplicar Filtros",
profileInfo: "Información de la Cuenta",
username: "Nombre de Usuario",
fullName: "Nombre Completo",
@@ -399,6 +426,15 @@ export const translations = {
userTitle: "Utilisateur",
themeColor: "Couleur du Thème",
personalizeColor: "Personnaliser la couleur",
advancedFilters: "Filtres Avancés",
filterByColor: "Couleur",
filterByAge: "Âge de l'article",
anyAge: "Tout âge",
lessThanMonth: "Moins d'un mois",
lessThan6Months: "Moins de 6 mois",
lessThanYear: "Moins d'un an",
older: "Plus ancien",
applyFilters: "Appliquer les filtres",
profileInfo: "Informations du Compte",
username: "Nom d'utilisateur",
fullName: "Nom Complet",
@@ -501,6 +537,15 @@ export const translations = {
userTitle: "Benutzer",
themeColor: "Themenfarbe",
personalizeColor: "Farbe anpassen",
advancedFilters: "Erweiterte Filter",
filterByColor: "Farbe",
filterByAge: "Artikelalter",
anyAge: "Jedes Alter",
lessThanMonth: "Weniger als 1 Monat",
lessThan6Months: "Weniger als 6 Monate",
lessThanYear: "Weniger als 1 Jahr",
older: "Älter",
applyFilters: "Filter anwenden",
profileInfo: "Kontoinformationen",
username: "Benutzername",
fullName: "Vollständiger Name",