From 7fa2eee3c72bea5ff3fbd08985a58ca1a71d5560 Mon Sep 17 00:00:00 2001 From: 230419 <230419@epvc.pt> Date: Mon, 27 Apr 2026 21:06:24 +0100 Subject: [PATCH] =?UTF-8?q?sec=C3=A7oes=20personalizadas=20!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 176 ++++++++++++++++++++++++++++++++++++++++++++++-- src/lib/i18n.js | 75 +++++++++++++++++++-- 2 files changed, 242 insertions(+), 9 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 86991c7..dd08ca9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,7 +6,8 @@ import { Edit2, Image as ImageIcon, Check, RotateCcw, Trash, PanelLeftClose, PanelLeftOpen, Sparkles, CloudSun, ArrowRight, Droplets, CheckCircle2, PieChart, History, - X, Download, Bell, Globe, Filter, ShoppingBag, Share2 + X, Download, Bell, Globe, Filter, ShoppingBag, Share2, + FolderOpen, Tag } from 'lucide-react'; import { @@ -66,6 +67,14 @@ export default function App() { const [sharedLookCopying, setSharedLookCopying] = useState(false); const [copiedLookId, setCopiedLookId] = useState(null); + // Estado para Secções + const [sections, setSections] = useState([]); + const [activeSectionFilter, setActiveSectionFilter] = useState('all'); + const [showSectionManager, setShowSectionManager] = useState(false); + const [newSectionName, setNewSectionName] = useState(''); + const [newSectionEmoji, setNewSectionEmoji] = useState(''); + const [itemSections, setItemSections] = useState([]); + const t = (key) => translations[language]?.[key] || translations['PT'][key] || key; // Mapeamento de nomes de cor (PT) para valores CSS @@ -151,6 +160,7 @@ export default function App() { } else { setItemColors([]); } + setItemSections(editingItem?.sections || []); }, [editingItem]); useEffect(() => { @@ -179,6 +189,7 @@ export default function App() { setUser(null); setClothes([]); setLooks([]); + setSections([]); setUserProfile({}); setDarkMode(false); setTheme('theme-indigo'); @@ -219,6 +230,12 @@ export default function App() { setLooks(snap.docs.map(d => ({ id: d.id, ...d.data() }))); }, (err) => console.error(err)); + // Secções + const sectionsCol = collection(db, 'artifacts', appId, 'users', user.uid, 'sections'); + const unsubSections = onSnapshot(sectionsCol, (snap) => { + setSections(snap.docs.map(d => ({ id: d.id, ...d.data() })).sort((a, b) => a.createdAt - b.createdAt)); + }, (err) => console.error(err)); + // Profile const profileDoc = doc(db, 'artifacts', appId, 'users', user.uid, 'profile', 'data'); const unsubProfile = onSnapshot(profileDoc, (snap) => { @@ -236,7 +253,7 @@ export default function App() { else setUserProfile({}); }, (err) => console.error(err)); - return () => { unsubClothes(); unsubLooks(); unsubProfile(); }; + return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); }; }, [user]); // Fetch Weather Data @@ -278,6 +295,35 @@ export default function App() { const wishlistClothes = useMemo(() => clothes.filter(c => c.status === 'wishlist'), [clothes]); const availableForLooks = useMemo(() => clothes.filter(c => c.status === 'active' || c.status === 'wishlist'), [clothes]); + // CRUD de Secções + const saveSection = async () => { + if (!newSectionName.trim() || !user) return; + const sectionsCol = collection(db, 'artifacts', appId, 'users', user.uid, 'sections'); + await addDoc(sectionsCol, { + name: newSectionName.trim(), + emoji: newSectionEmoji.trim() || '💼', + createdAt: new Date().getTime() + }); + setNewSectionName(''); + setNewSectionEmoji(''); + }; + + const deleteSection = async (id) => { + if (!window.confirm(t('confirmDeleteSection'))) return; + const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'sections', id); + await deleteDoc(docRef); + // Remover a secção de todas as peças que a tinham + const batch = writeBatch(db); + clothes.forEach(item => { + if (item.sections && item.sections.includes(id)) { + const itemRef = doc(db, 'artifacts', appId, 'users', user.uid, 'clothes', item.id); + batch.update(itemRef, { sections: item.sections.filter(s => s !== id) }); + } + }); + await batch.commit(); + if (activeSectionFilter === id) setActiveSectionFilter('all'); + }; + const baseClothes = view === 'wishlist' ? wishlistClothes : activeClothes; const availableColors = useMemo(() => { @@ -313,6 +359,7 @@ export default function App() { (c.color || "").toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory = categoryFilter === 'Todos' || categoryFilter === t('all') || c.category === categoryFilter; const matchesColor = !colorFilter || (c.color && c.color.includes(colorFilter)); + const matchesSection = activeSectionFilter === 'all' || (c.sections && c.sections.includes(activeSectionFilter)); let matchesAge = true; if (ageFilter !== 'any') { @@ -324,9 +371,9 @@ export default function App() { else if (ageFilter === 'older') matchesAge = days > 365; } - return matchesSearch && matchesCategory && matchesColor && matchesAge; + return matchesSearch && matchesCategory && matchesColor && matchesAge && matchesSection; }); - }, [baseClothes, searchTerm, categoryFilter, colorFilter, ageFilter, t]); + }, [baseClothes, searchTerm, categoryFilter, colorFilter, ageFilter, t, activeSectionFilter]); // Ações de Itens const handleItemAction = async (action, item) => { @@ -362,6 +409,7 @@ export default function App() { imageUrl: formData.get('imageUrl') || 'https://images.unsplash.com/photo-1521572267360-ee0c2909d518?q=80&w=500&auto=format&fit=crop', status: formData.get('isWishlist') ? 'wishlist' : (editingItem && editingItem.status === 'wishlist' ? 'active' : (editingItem ? editingItem.status : 'active')), favorite: editingItem ? (editingItem.favorite || false) : false, + sections: itemSections, updatedAt: new Date().getTime() }; @@ -868,6 +916,33 @@ export default function App() { + {/* Barra de Secções */} + {view === 'closet' && ( +
+ + {sections.map(sec => ( + + ))} + +
+ )} +
{filteredClothes.map(item => (
@@ -1149,6 +1224,33 @@ export default function App() {
setImageUrlDraft(v)} /> + {/* Campo de Secções */} + {sections.length > 0 && ( +
+ +
+ {sections.map(sec => ( + + ))} +
+
+ )} +
+ {/* Modal de Gestão de Secções */} + {showSectionManager && ( +
setShowSectionManager(false)}> + e.stopPropagation()}> +
+

+ {t('manageSections')} +

+ +
+ + {/* Criar nova secção */} +
+ setNewSectionEmoji(e.target.value)} + placeholder={t('emojiPlaceholder')} + maxLength={2} + className={`w-16 p-3 rounded-xl border-none outline-none font-bold text-center text-lg ${darkMode ? 'bg-gray-700 text-white' : 'bg-white'} shadow-sm`} + /> + setNewSectionName(e.target.value)} + placeholder={t('sectionPlaceholder')} + onKeyDown={e => e.key === 'Enter' && saveSection()} + className={`flex-1 p-3 rounded-xl border-none outline-none font-bold ${darkMode ? 'bg-gray-700 text-white' : 'bg-white'} shadow-sm`} + /> + +
+ + {/* Lista de secções */} +
+ {sections.length === 0 ? ( +
{t('noSections')}
+ ) : sections.map(sec => ( +
+ {sec.emoji} +
+

{sec.name}

+

+ {clothes.filter(c => c.sections && c.sections.includes(sec.id)).length} peça(s) +

+
+ +
+ ))} +
+ + +
+
+ )} + {/* Modal de Filtros Avançados */} {showClosetFilters && (
setShowClosetFilters(false)}> diff --git a/src/lib/i18n.js b/src/lib/i18n.js index 14917df..4ea89ea 100644 --- a/src/lib/i18n.js +++ b/src/lib/i18n.js @@ -117,7 +117,20 @@ export const translations = { dob: "Data de Nascimento", bio: "Bio / Sobre mim", optional: "(Opcional)", - saving: "A guardar..." + saving: "A guardar...", + sections: "Secções", + manageSections: "Gerir Secções", + newSection: "Nova Secção", + sectionName: "Nome da Secção", + sectionEmoji: "Emoji", + noSections: "Nenhuma secção criada ainda.", + addSection: "Adicionar Secção", + deleteSection: "Apagar", + assignSections: "Atribuir a Secções", + allSections: "Todas", + confirmDeleteSection: "Apagar esta secção?", + sectionPlaceholder: "Ex: Trabalho, Festa...", + emojiPlaceholder: "Ex: 💼" }, EN: { loginModeIntro: "The Future of Your Style", @@ -237,7 +250,20 @@ export const translations = { dob: "Date of Birth", bio: "Bio / About me", optional: "(Optional)", - saving: "Saving..." + saving: "Saving...", + sections: "Sections", + manageSections: "Manage Sections", + newSection: "New Section", + sectionName: "Section Name", + sectionEmoji: "Emoji", + noSections: "No sections created yet.", + addSection: "Add Section", + deleteSection: "Delete", + assignSections: "Assign to Sections", + allSections: "All", + confirmDeleteSection: "Delete this section?", + sectionPlaceholder: "E.g.: Work, Party...", + emojiPlaceholder: "E.g.: 💼" }, ES: { loginModeIntro: "El Futuro de Tu Estilo", @@ -357,7 +383,20 @@ export const translations = { dob: "Fecha de Nacimiento", bio: "Bio / Sobre mí", optional: "(Opcional)", - saving: "Guardando..." + saving: "Guardando...", + sections: "Secciones", + manageSections: "Gestionar Secciones", + newSection: "Nueva Sección", + sectionName: "Nombre de la Sección", + sectionEmoji: "Emoji", + noSections: "Aún no hay secciones creadas.", + addSection: "Añadir Sección", + deleteSection: "Eliminar", + assignSections: "Asignar a Secciones", + allSections: "Todas", + confirmDeleteSection: "¿Eliminar esta sección?", + sectionPlaceholder: "Ej: Trabajo, Fiesta...", + emojiPlaceholder: "Ej: 💼" }, FR: { loginModeIntro: "Le Futur de Ton Style", @@ -477,7 +516,20 @@ export const translations = { dob: "Date de Naissance", bio: "Bio / À propos", optional: "(Optionnel)", - saving: "Enregistrement..." + saving: "Enregistrement...", + sections: "Sections", + manageSections: "Gérer les Sections", + newSection: "Nouvelle Section", + sectionName: "Nom de la Section", + sectionEmoji: "Emoji", + noSections: "Aucune section créée pour l'instant.", + addSection: "Ajouter une Section", + deleteSection: "Supprimer", + assignSections: "Attribuer aux Sections", + allSections: "Toutes", + confirmDeleteSection: "Supprimer cette section ?", + sectionPlaceholder: "Ex: Travail, Fête...", + emojiPlaceholder: "Ex: 💼" }, DE: { loginModeIntro: "Die Zukunft deines Stils", @@ -597,6 +649,19 @@ export const translations = { dob: "Geburtsdatum", bio: "Biografie / Über mich", optional: "(Optional)", - saving: "Speichern..." + saving: "Speichern...", + sections: "Bereiche", + manageSections: "Bereiche verwalten", + newSection: "Neuer Bereich", + sectionName: "Bereichsname", + sectionEmoji: "Emoji", + noSections: "Noch keine Bereiche erstellt.", + addSection: "Bereich hinzufügen", + deleteSection: "Löschen", + assignSections: "Bereichen zuweisen", + allSections: "Alle", + confirmDeleteSection: "Diesen Bereich löschen?", + sectionPlaceholder: "Zb: Arbeit, Party...", + emojiPlaceholder: "Zb: 💼" } };