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 */}
+
+
+ {/* 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: 💼"
}
};