diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index 53b6274..43c8a6a 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -11,8 +11,10 @@ import FootprintChart from "@/components/charts/FootprintChart"; import ComparisonSection from "@/components/dashboard/ComparisonSection"; import ShareButton from "@/components/ui/ShareButton"; import { getUserFootprints } from "@/lib/firebase/firestore"; +import { deleteActivitys } from "@/lib/firebase/firestore"; import { exportToCSV, ActivityHistoryEntry } from "@/utils/exportCSV"; import { ArrowDownTrayIcon } from "@heroicons/react/24/outline"; +import ConfirmDialog from "../ui/ConfirmDialog"; import Spinner from "@/components/ui/Spinner"; type SortOption = "newest" | "oldest" | "highest_impact" | "lowest_impact"; @@ -88,6 +90,7 @@ interface DashboardProps { onNavigate: (page: PageType) => void; sortPreference: SortOption; onSortChange: (sort: SortOption) => void; + onDeleteActivity: (id:string, dateString: string, activities: any, totalCO2: number) => void; } export default function Dashboard({ @@ -96,12 +99,15 @@ export default function Dashboard({ onNavigate, sortPreference, onSortChange, + onDeleteActivity }: DashboardProps) { const { user } = useAuth(); const [dashboardData, setDashboardData] = useState( null ); const [loading, setLoading] = useState(true); + const [confirmOpen, setConfirmOpen] = useState(false); + const [toDeleteEntry, setToDeleteEntry] = useState(null); const [exportStatus, setExportStatus] = useState<{ show: boolean; success: boolean; @@ -214,6 +220,33 @@ export default function Dashboard({ } }, [propDashboardData]); + const handleDeleteClick = (entry: any) => { + setToDeleteEntry(entry); + setConfirmOpen(true); + }; + + const handleConfirmDelete = async () => { + if (!toDeleteEntry) return; + setConfirmOpen(false); + + try { + if (onDeleteActivity) { + await onDeleteActivity(toDeleteEntry.id, toDeleteEntry.timestamp.toString(), toDeleteEntry.activities, toDeleteEntry.result.totalCO2); + } else { + // fallback: try direct firestore helper + await deleteActivitys?.(toDeleteEntry.id); + } + } catch (err) { + console.error("Error deleting activity", err); + } finally { + setToDeleteEntry(null); + } + }; + + const handleCancelDelete = () => { + setConfirmOpen(false); + setToDeleteEntry(null); + } // Handle CSV export const handleExportCSV = () => { const result = exportToCSV(activityHistory as ActivityHistoryEntry[]); @@ -411,21 +444,41 @@ export default function Dashboard({ +{formatCO2Amount(entry.result.totalCO2)} -
- {Object.entries(entry.activities).map( - ([activity, value]) => - (value as number) > 0 ? ( - - {activity}: {value as number} - - ) : null - )} + +
+
+ {Object.entries(entry.activities).map( + ([activity, value]) => + (value as number) > 0 ? ( + + {activity}: {value as number} + + ) : null + )} +
+ + {/* delete icon */} +
))} + ) : ( // EMPTY STATE FOR ACTIVITY HISTORY diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index e618bb0..6af471d 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -11,7 +11,7 @@ import GoalsPanel from "@/components/gamification/GoalsPanel"; import BadgeDisplay from "@/components/gamification/BadgeDisplay"; import { ActivityInput } from "@/types"; import { calculateCarbonFootprint } from "@/lib/calculations/carbonFootprint"; -import { saveCarbonFootprint, saveActivity } from "@/lib/firebase/firestore"; +import { saveCarbonFootprint, saveActivity, deleteActivitys } from "@/lib/firebase/firestore"; import { ShortcutsModal } from "../ui/ShortcutsModal"; import { useKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts"; import QuickActionsFAB from "@/components/ui/QuickActionsFAB"; @@ -28,6 +28,7 @@ export default function AppLayout() { const [currentPage, setCurrentPage] = useState("dashboard"); const [todayFootprint, setTodayFootprint] = useState(0); const [successToast, setSuccessToast] = useState(null); + const [failToast, setFailToast] = useState(null); const [isLoading, setIsLoading] = useState(false); const [showSuccess, setShowSuccess] = useState(false); const [dashboardData, setDashboardData] = useState(null); @@ -65,6 +66,52 @@ useEffect(() => { localStorage.setItem(LOCAL_STORAGE_KEY, newSort); }; + const handleDeleteActivity = async ( + id:string, + dateString: string, + activities: any, + totalCO2: number, + customToastMessage?: string + ) => { + + const newTodayFootprint = todayFootprint - totalCO2; + setTodayFootprint(newTodayFootprint); + + // In development mode, simulate saving with shorter delay + if (process.env.NODE_ENV === "development") { + setActivityHistory((prev) => prev.filter((activity) => activity.id !== id)); + setSuccessToast(customToastMessage || "Activities saved successfully!"); + setTimeout(() => setSuccessToast(null), 3000); + return; + } + + try{ + // Loop through activities and delete each from Firestore + if (activities) { + const deletionPromises = Object.entries(activities) + .filter(([_, value]) => value as number > 0) + .map(([activityType, _]) => + deleteActivitys({ + rawDateString: dateString, + userId: user!.id, + activityType: activityType + }) + ); + + // Wait for all deletions to finish + await Promise.all(deletionPromises); + + // Now update state and show toast + setActivityHistory((prev) => prev.filter((activity) => activity.id !== id)); + setSuccessToast(customToastMessage || "Activity deleted successfully!"); + setTimeout(() => setSuccessToast(null), 3000); + } + }catch(error){ + setTodayFootprint((prev) => prev + totalCO2); + setFailToast(customToastMessage || "An error occurred while deleting activity."); + setTimeout(() => setFailToast(null), 3000); + } + } const getSortedActivityHistory = () => { // We create a shallow copy to ensure we don't mutate the original state const sortedList = [...activityHistory]; @@ -269,6 +316,7 @@ useEffect(() => { sortPreference={sortPreference} onSortChange={handleSortChange} onNavigate={setCurrentPage} + onDeleteActivity={handleDeleteActivity} /> ); @@ -405,6 +453,15 @@ useEffect(() => { )} + {/* Failed Toast */} + {failToast && ( +
+
+ + {failToast} +
+
+ )} {/* Mobile spacing for bottom navigation */}
diff --git a/src/components/ui/ConfirmDialog.tsx b/src/components/ui/ConfirmDialog.tsx index 0378c05..b5939d5 100644 --- a/src/components/ui/ConfirmDialog.tsx +++ b/src/components/ui/ConfirmDialog.tsx @@ -151,4 +151,4 @@ export default function ConfirmDialog({ ); -} +} \ No newline at end of file diff --git a/src/lib/firebase/firestore.ts b/src/lib/firebase/firestore.ts index f9c6cb5..18ff37f 100644 --- a/src/lib/firebase/firestore.ts +++ b/src/lib/firebase/firestore.ts @@ -53,6 +53,37 @@ export const saveActivity = async (activity: Omit) => { }); }; +export const deleteActivitys = async (args: { + rawDateString: string; + userId: string; + activityType: string; +}) => { + const { rawDateString, userId, activityType} = args; + // convert incoming date string back into Timestamp to match stored data + const date =new Date(rawDateString); + const start = Timestamp.fromDate(new Date(date)); // start time + const end = Timestamp.fromDate(new Date(date.getTime() + 1000)); // +1 second window + + // Query for matching documents (change this in the future if an unique is implemented) + const q = query( + collection(db, "activities"), + where("date", ">=", start), + where("date", "<", end), + where("type", "==", activityType), + where("userId", "==", userId) + ); + + const snap = await getDocs(q); + + const deletes = snap.docs.map(doc => deleteDoc(doc.ref)); + await Promise.all(deletes); + + if (deletes.length === 0) { + console.error("No matching activity found in DB to delete."); + throw new Error("No matching activity found to delete."); + } +}; + export const getUserActivities = async (userId: string, days: number = 30): Promise => { const activitiesRef = collection(db, 'activities'); const startDate = new Date();