armario 1
This commit is contained in:
141
src/App.jsx
141
src/App.jsx
@@ -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 => (
|
||||
<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')}`}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex gap-3 w-full xl:w-auto">
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<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)}>
|
||||
|
||||
Reference in New Issue
Block a user