diff --git a/apps/mobile/src/app/(tabs)/_layout.tsx b/apps/mobile/src/app/(tabs)/_layout.tsx
index 6ab4348..407aa3e 100644
--- a/apps/mobile/src/app/(tabs)/_layout.tsx
+++ b/apps/mobile/src/app/(tabs)/_layout.tsx
@@ -83,12 +83,6 @@ export default function TabLayout() {
title: "Plans",
}}
/>
-
(null);
- const [history, setHistory] = useState([]);
-
- const fetchAttendance = async () => {
- try {
- setLoading(true);
- const token = await getToken();
- if (!token) return;
-
- log.debug("Fetching attendance history");
- const data = await attendanceApi.getHistory(token);
- setHistory(data);
-
- // Check if there's an active check-in (latest one has no checkOutTime)
- if (data.length > 0 && !data[0].checkOutTime) {
- setActiveCheckIn(data[0]);
- } else {
- setActiveCheckIn(null);
- }
- } catch (error) {
- log.error("Failed to fetch attendance", error);
- Alert.alert("Error", "Failed to load attendance data");
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- fetchAttendance();
- }, []);
-
- const handleCheckIn = async () => {
- try {
- const token = await getToken();
- if (!token) return;
-
- await attendanceApi.checkIn("gym", token);
- clearStatisticsCache();
- fetchAttendance();
- Alert.alert("Success", "Checked in successfully!");
- } catch (error: unknown) {
- log.error("Failed to check in", error);
- Alert.alert("Error", getErrorMessage(error, "Failed to check in"));
- }
- };
-
- const handleCheckOut = async () => {
- try {
- const token = await getToken();
- if (!token) return;
-
- await attendanceApi.checkOut(token);
- clearStatisticsCache();
- fetchAttendance();
- Alert.alert("Success", "Checked out successfully!");
- } catch (error: unknown) {
- log.error("Failed to check out", error);
- Alert.alert("Error", getErrorMessage(error, "Failed to check out"));
- }
- };
-
- if (loading && !history.length) {
- return (
-
-
-
- );
- }
-
- return (
-
- {/* Header */}
-
-
- Attendance
-
-
- {activeCheckIn
- ? "You're crushing it today!"
- : history.length === 0
- ? "Ready to start your fitness journey?"
- : "Track your gym visits and build streaks!"}
-
-
-
- {/* Check In/Out Section */}
-
- {activeCheckIn ? (
-
-
-
-
-
-
-
-
- ✅ Currently Checked In
-
-
- Since{" "}
- {new Date(activeCheckIn.checkInTime).toLocaleTimeString(
- [],
- {
- hour: "2-digit",
- minute: "2-digit",
- },
- )}
-
-
-
-
-
-
- ) : (
-
- )}
-
-
- {/* Attendance Calendar */}
-
-
-
-
-
-
-
- {/* Recent History */}
-
-
- {history.length === 0 ? (
-
-
- 📍
-
- No attendance history yet
-
-
- Check in to start building your streak! 🔥
-
-
-
- ) : (
-
- {history.slice(0, 10).map((record, index) => {
- const checkIn = new Date(record.checkInTime);
- const checkOut = record.checkOutTime
- ? new Date(record.checkOutTime)
- : null;
- const duration = checkOut
- ? Math.round((checkOut.getTime() - checkIn.getTime()) / 60000)
- : null;
-
- return (
-
-
-
-
-
-
-
-
- {checkIn.toLocaleDateString()}
-
-
- {checkIn.toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- })}
- {checkOut &&
- ` - ${checkOut.toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- })}`}
-
-
-
- {duration && (
-
- )}
-
-
- );
- })}
-
- )}
-
-
- {/* Bottom Spacer */}
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- },
- centered: {
- flex: 1,
- justifyContent: "center",
- alignItems: "center",
- },
- content: {
- paddingBottom: 20,
- },
- header: {
- paddingHorizontal: 24,
- paddingTop: 60,
- paddingBottom: 24,
- },
- section: {
- paddingHorizontal: 24,
- marginBottom: 24,
- },
- activeCard: {
- padding: 20,
- borderRadius: 20,
- },
- activeHeader: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- },
- activeHeaderLeft: {
- flexDirection: "row",
- alignItems: "center",
- flex: 1,
- },
- emptyState: {
- alignItems: "center",
- paddingVertical: 40,
- paddingHorizontal: 20,
- },
- historyList: {
- gap: 12,
- },
- historyItem: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- },
- historyLeft: {
- flexDirection: "row",
- alignItems: "center",
- flex: 1,
- },
-});
diff --git a/apps/mobile/src/components/AttendanceCalendar.tsx b/apps/mobile/src/components/AttendanceCalendar.tsx
deleted file mode 100644
index 1f6f923..0000000
--- a/apps/mobile/src/components/AttendanceCalendar.tsx
+++ /dev/null
@@ -1,303 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
-import { LinearGradient } from "expo-linear-gradient";
-import { Ionicons } from "@expo/vector-icons";
-import { theme } from "../styles/theme";
-
-interface AttendanceRecord {
- id: string;
- checkInTime: string;
- checkOutTime?: string | null;
- duration?: number | null;
-}
-
-interface AttendanceCalendarProps {
- attendanceRecords: AttendanceRecord[];
-}
-
-export function AttendanceCalendar({
- attendanceRecords,
-}: AttendanceCalendarProps) {
- const [currentMonth, setCurrentMonth] = useState(new Date());
- const [calendarDays, setCalendarDays] = useState<
- Array<{ date: Date | null; hasAttendance: boolean; isToday: boolean }>
- >([]);
-
- useEffect(() => {
- generateCalendar(currentMonth);
- }, [currentMonth, attendanceRecords]);
-
- const generateCalendar = (month: Date) => {
- const year = month.getFullYear();
- const monthIndex = month.getMonth();
-
- // Get first day of month and number of days
- const firstDay = new Date(year, monthIndex, 1);
- const lastDay = new Date(year, monthIndex + 1, 0);
- const daysInMonth = lastDay.getDate();
- const startingDayOfWeek = firstDay.getDay();
-
- // Create attendance lookup set
- const attendanceDates = new Set(
- attendanceRecords.map((record) =>
- new Date(record.checkInTime).toDateString(),
- ),
- );
-
- const today = new Date().toDateString();
-
- // Build calendar array
- const days: Array<{
- date: Date | null;
- hasAttendance: boolean;
- isToday: boolean;
- }> = [];
-
- // Add empty cells for days before month starts
- for (let i = 0; i < startingDayOfWeek; i++) {
- days.push({ date: null, hasAttendance: false, isToday: false });
- }
-
- // Add days of the month
- for (let day = 1; day <= daysInMonth; day++) {
- const date = new Date(year, monthIndex, day);
- const dateString = date.toDateString();
- days.push({
- date,
- hasAttendance: attendanceDates.has(dateString),
- isToday: dateString === today,
- });
- }
-
- setCalendarDays(days);
- };
-
- const goToPreviousMonth = () => {
- setCurrentMonth(
- new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1),
- );
- };
-
- const goToNextMonth = () => {
- setCurrentMonth(
- new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1),
- );
- };
-
- const monthName = currentMonth.toLocaleDateString("en-US", {
- month: "long",
- year: "numeric",
- });
-
- return (
-
-
- {/* Header */}
-
- Attendance Calendar
-
-
- {/* Month Navigation */}
-
-
-
-
- {monthName}
-
-
-
-
-
- {/* Day Headers */}
-
- {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (
-
- {day}
-
- ))}
-
-
- {/* Calendar Grid */}
-
- {calendarDays.map((day, index) => (
-
- {day.date ? (
-
-
- {day.date.getDate()}
-
- {day.hasAttendance && }
-
- ) : (
-
- )}
-
- ))}
-
-
- {/* Legend */}
-
-
-
- Attended
-
-
-
- Today
-
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- paddingHorizontal: 20,
- marginBottom: 20,
- },
- card: {
- borderRadius: theme.borderRadius["2xl"],
- padding: 20,
- },
- header: {
- marginBottom: 16,
- },
- title: {
- fontSize: theme.typography.fontSize.xl,
- fontWeight: theme.typography.fontWeight.bold,
- color: theme.colors.gray800,
- },
- monthNav: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 16,
- paddingHorizontal: 8,
- },
- navButton: {
- padding: 8,
- },
- monthText: {
- fontSize: theme.typography.fontSize.lg,
- fontWeight: theme.typography.fontWeight.semibold,
- color: theme.colors.gray700,
- },
- dayHeaders: {
- flexDirection: "row",
- marginBottom: 8,
- },
- dayHeader: {
- flex: 1,
- textAlign: "center",
- fontSize: theme.typography.fontSize.xs,
- fontWeight: theme.typography.fontWeight.semibold,
- color: theme.colors.gray500,
- },
- calendarGrid: {
- flexDirection: "row",
- flexWrap: "wrap",
- },
- dayCell: {
- width: `${100 / 7}%`,
- aspectRatio: 1,
- padding: 2,
- },
- dayContent: {
- flex: 1,
- justifyContent: "center",
- alignItems: "center",
- borderRadius: theme.borderRadius.md,
- position: "relative",
- },
- todayContent: {
- backgroundColor: theme.colors.primaryLight,
- borderWidth: 1,
- borderColor: theme.colors.primary,
- },
- attendanceContent: {
- backgroundColor: theme.colors.successLight,
- },
- emptyCell: {
- flex: 1,
- },
- dayText: {
- fontSize: theme.typography.fontSize.sm,
- color: theme.colors.gray700,
- fontWeight: theme.typography.fontWeight.medium,
- },
- todayText: {
- color: theme.colors.white,
- fontWeight: theme.typography.fontWeight.bold,
- },
- attendanceText: {
- color: theme.colors.white,
- fontWeight: theme.typography.fontWeight.bold,
- },
- attendanceDot: {
- position: "absolute",
- bottom: 2,
- width: 4,
- height: 4,
- borderRadius: 2,
- backgroundColor: theme.colors.white,
- },
- legend: {
- flexDirection: "row",
- justifyContent: "center",
- gap: 20,
- marginTop: 16,
- paddingTop: 16,
- borderTopWidth: 1,
- borderTopColor: theme.colors.gray200,
- },
- legendItem: {
- flexDirection: "row",
- alignItems: "center",
- gap: 6,
- },
- legendDotAttendance: {
- width: 12,
- height: 12,
- borderRadius: 6,
- backgroundColor: theme.colors.successLight,
- },
- legendDotToday: {
- width: 12,
- height: 12,
- borderRadius: 6,
- backgroundColor: theme.colors.primaryLight,
- borderWidth: 1,
- borderColor: theme.colors.primary,
- },
- legendText: {
- fontSize: theme.typography.fontSize.xs,
- color: theme.colors.gray600,
- },
-});
diff --git a/apps/mobile/src/components/CustomTabBar.tsx b/apps/mobile/src/components/CustomTabBar.tsx
index 301bed3..c6bc00f 100644
--- a/apps/mobile/src/components/CustomTabBar.tsx
+++ b/apps/mobile/src/components/CustomTabBar.tsx
@@ -49,8 +49,6 @@ export function CustomTabBar({
return focused ? "home" : "home-outline";
case "goals":
return focused ? "trophy" : "trophy-outline";
- case "attendance":
- return focused ? "calendar" : "calendar-outline";
case "recommendations":
return focused ? "sparkles" : "sparkles-outline";
case "profile":
@@ -66,8 +64,6 @@ export function CustomTabBar({
return "Home";
case "goals":
return "Goals";
- case "attendance":
- return "Attendance";
case "recommendations":
return "Plans";
case "profile":