From e377258671c7e0019569d08146fff497148f243e Mon Sep 17 00:00:00 2001
From: 230419 <230419@epvc.pt>
Date: Tue, 28 Apr 2026 17:11:43 +0100
Subject: [PATCH] notificacoes, seccoes feito completo e botao lavandaria feito
---
src/App.jsx | 274 +++++++++++++++++++++++++++++++-----
src/components/ui/Badge.jsx | 2 +-
src/components/ui/Card.jsx | 9 +-
src/lib/i18n.js | 5 +
vite.config.js | 3 +-
5 files changed, 255 insertions(+), 38 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
index dd08ca9..5628184 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -74,6 +74,14 @@ export default function App() {
const [newSectionName, setNewSectionName] = useState('');
const [newSectionEmoji, setNewSectionEmoji] = useState('');
const [itemSections, setItemSections] = useState([]);
+ const [lookSections, setLookSections] = useState([]);
+ const [editingSectionId, setEditingSectionId] = useState(null);
+ const [editSectionName, setEditSectionName] = useState('');
+ const [editSectionEmoji, setEditSectionEmoji] = useState('');
+
+ const [notifications, setNotifications] = useState([]);
+ const [showNotificationsModal, setShowNotificationsModal] = useState(false);
+ const [toastMessage, setToastMessage] = useState(null);
const t = (key) => translations[language]?.[key] || translations['PT'][key] || key;
@@ -163,6 +171,10 @@ export default function App() {
setItemSections(editingItem?.sections || []);
}, [editingItem]);
+ useEffect(() => {
+ setLookSections(editingLook?.sections || []);
+ }, [editingLook]);
+
useEffect(() => {
document.documentElement.classList.remove('theme-indigo', 'theme-rose', 'theme-emerald', 'theme-amber', 'theme-slate');
// Ecrã de login/registo: sempre theme-indigo, independentemente das preferências do utilizador
@@ -253,7 +265,13 @@ export default function App() {
else setUserProfile({});
}, (err) => console.error(err));
- return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); };
+ // Notificações
+ const notifCol = collection(db, 'artifacts', appId, 'users', user.uid, 'notifications');
+ const unsubNotif = onSnapshot(notifCol, (snap) => {
+ setNotifications(snap.docs.map(d => ({ id: d.id, ...d.data() })).sort((a, b) => b.createdAt - a.createdAt));
+ }, (err) => console.error(err));
+
+ return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); unsubNotif(); };
}, [user]);
// Fetch Weather Data
@@ -293,9 +311,25 @@ 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 wishlistClothes = useMemo(() => clothes.filter(c => c.status === 'wishlist'), [clothes]);
- const availableForLooks = useMemo(() => clothes.filter(c => c.status === 'active' || c.status === 'wishlist'), [clothes]);
+ const availableForLooks = useMemo(() => clothes.filter(c => {
+ const isAvailable = c.status !== 'trash';
+ const matchesSection = activeSectionFilter === 'all' || (c.sections && c.sections.includes(activeSectionFilter));
+ return isAvailable && matchesSection;
+ }), [clothes, activeSectionFilter]);
// CRUD de Secções
+ const updateSection = async () => {
+ if (!editSectionName.trim() || !user || !editingSectionId) return;
+ const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'sections', editingSectionId);
+ await updateDoc(docRef, {
+ name: editSectionName.trim(),
+ emoji: editSectionEmoji.trim() || '💼'
+ });
+ setEditingSectionId(null);
+ setEditSectionName('');
+ setEditSectionEmoji('');
+ };
+
const saveSection = async () => {
if (!newSectionName.trim() || !user) return;
const sectionsCol = collection(db, 'artifacts', appId, 'users', user.uid, 'sections');
@@ -320,6 +354,12 @@ export default function App() {
batch.update(itemRef, { sections: item.sections.filter(s => s !== id) });
}
});
+ looks.forEach(look => {
+ if (look.sections && look.sections.includes(id)) {
+ const lookRef = doc(db, 'artifacts', appId, 'users', user.uid, 'looks', look.id);
+ batch.update(lookRef, { sections: look.sections.filter(s => s !== id) });
+ }
+ });
await batch.commit();
if (activeSectionFilter === id) setActiveSectionFilter('all');
};
@@ -449,6 +489,7 @@ export default function App() {
const lookData = {
name: fd.get('lookName'),
items: selectedForLook,
+ sections: lookSections,
updatedAt: new Date().getTime()
};
try {
@@ -533,6 +574,19 @@ export default function App() {
createdAt: new Date().getTime(),
updatedAt: new Date().getTime(),
});
+
+ // Notify the owner
+ if (sharedLookData.ownerUid && sharedLookData.ownerUid !== user.uid) {
+ const notificationsCol = collection(db, 'artifacts', appId, 'users', sharedLookData.ownerUid, 'notifications');
+ await addDoc(notificationsCol, {
+ type: 'look_copied',
+ lookName: sharedLookData.lookName,
+ copiedByEmail: userProfile?.username || user.email || 'Alguém',
+ createdAt: new Date().getTime(),
+ read: false
+ });
+ }
+
setShowSharedLookModal(false);
setSharedLookData(null);
setView('outfits');
@@ -545,7 +599,6 @@ export default function App() {
};
const sendLookToLaundry = async (look) => {
- if (!window.confirm(t('confirmSendLookToLaundry') || 'Enviar todas as peças deste look para a lavandaria?')) return;
setLoading(true);
try {
const batch = writeBatch(db);
@@ -554,7 +607,8 @@ export default function App() {
batch.update(docRef, { status: 'laundry' });
});
await batch.commit();
- alert(t('lookSentToLaundry') || 'Peças enviadas para a lavandaria!');
+ setToastMessage(t('lookSentToLaundry') || 'Peças enviadas para a lavandaria!');
+ setTimeout(() => setToastMessage(null), 3000);
} catch (e) { console.error(e); }
finally { setLoading(false); }
};
@@ -810,6 +864,12 @@ export default function App() {
+
@@ -917,7 +977,7 @@ export default function App() {
{/* Barra de Secções */}
- {view === 'closet' && (
+ {(view === 'closet' || view === 'outfits') && (