remove attendance tab and screen from mobile navigation
This commit is contained in:
parent
0ccf59344e
commit
ad3eba48b0
@ -83,12 +83,6 @@ export default function TabLayout() {
|
|||||||
title: "Plans",
|
title: "Plans",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
|
||||||
name="attendance"
|
|
||||||
options={{
|
|
||||||
title: "Attendance",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="profile"
|
name="profile"
|
||||||
options={{
|
options={{
|
||||||
|
|||||||
@ -1,348 +0,0 @@
|
|||||||
import {
|
|
||||||
View,
|
|
||||||
Text,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableOpacity,
|
|
||||||
ActivityIndicator,
|
|
||||||
ScrollView,
|
|
||||||
Alert,
|
|
||||||
} from "react-native";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useAuth } from "@clerk/clerk-expo";
|
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
|
||||||
import { useTheme } from "../../contexts/ThemeContext";
|
|
||||||
import { MinimalCard } from "../../components/MinimalCard";
|
|
||||||
import { SectionHeader } from "../../components/SectionHeader";
|
|
||||||
import { MinimalButton } from "../../components/MinimalButton";
|
|
||||||
import { Badge } from "../../components/Badge";
|
|
||||||
import { IconContainer } from "../../components/IconContainer";
|
|
||||||
import { attendanceApi, Attendance } from "../../api/attendance";
|
|
||||||
import { AttendanceCalendar } from "../../components/AttendanceCalendar";
|
|
||||||
import { useStatistics } from "../../contexts/StatisticsContext";
|
|
||||||
import { getErrorMessage } from "../../utils/error-helpers";
|
|
||||||
import log from "../../utils/logger";
|
|
||||||
|
|
||||||
export default function AttendanceScreen() {
|
|
||||||
const { getToken, userId } = useAuth();
|
|
||||||
const { colors, typography } = useTheme();
|
|
||||||
const { clearCache: clearStatisticsCache } = useStatistics();
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [activeCheckIn, setActiveCheckIn] = useState<Attendance | null>(null);
|
|
||||||
const [history, setHistory] = useState<Attendance[]>([]);
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<View style={[styles.centered, { backgroundColor: colors.background }]}>
|
|
||||||
<ActivityIndicator size="large" color={colors.primary} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
style={[styles.container, { backgroundColor: colors.background }]}
|
|
||||||
contentContainerStyle={styles.content}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<View style={styles.header}>
|
|
||||||
<Text
|
|
||||||
style={[typography.h1, { color: colors.textPrimary, fontSize: 32 }]}
|
|
||||||
>
|
|
||||||
Attendance
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
typography.body,
|
|
||||||
{ color: colors.textSecondary, marginTop: 8 },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{activeCheckIn
|
|
||||||
? "You're crushing it today!"
|
|
||||||
: history.length === 0
|
|
||||||
? "Ready to start your fitness journey?"
|
|
||||||
: "Track your gym visits and build streaks!"}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Check In/Out Section */}
|
|
||||||
<View style={styles.section}>
|
|
||||||
{activeCheckIn ? (
|
|
||||||
<MinimalCard variant="bordered" style={styles.activeCard}>
|
|
||||||
<View style={styles.activeHeader}>
|
|
||||||
<View style={styles.activeHeaderLeft}>
|
|
||||||
<IconContainer
|
|
||||||
variant="colored"
|
|
||||||
backgroundColor={`${colors.success}20`}
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
<Ionicons
|
|
||||||
name="checkmark-circle"
|
|
||||||
size={28}
|
|
||||||
color={colors.success}
|
|
||||||
/>
|
|
||||||
</IconContainer>
|
|
||||||
<View style={{ marginLeft: 12 }}>
|
|
||||||
<Text style={[typography.h3, { color: colors.textPrimary }]}>
|
|
||||||
✅ Currently Checked In
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
typography.caption,
|
|
||||||
{ color: colors.textTertiary, marginTop: 2 },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Since{" "}
|
|
||||||
{new Date(activeCheckIn.checkInTime).toLocaleTimeString(
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<MinimalButton
|
|
||||||
title="Check Out"
|
|
||||||
onPress={handleCheckOut}
|
|
||||||
variant="danger"
|
|
||||||
size="lg"
|
|
||||||
style={{ marginTop: 16 }}
|
|
||||||
/>
|
|
||||||
</MinimalCard>
|
|
||||||
) : (
|
|
||||||
<MinimalButton
|
|
||||||
title="💪 Check In"
|
|
||||||
onPress={handleCheckIn}
|
|
||||||
variant="primary"
|
|
||||||
size="lg"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Attendance Calendar */}
|
|
||||||
<View style={styles.section}>
|
|
||||||
<SectionHeader title="📅 Calendar" />
|
|
||||||
<MinimalCard variant="default" style={{ borderRadius: 20 }}>
|
|
||||||
<AttendanceCalendar attendanceRecords={history} />
|
|
||||||
</MinimalCard>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Recent History */}
|
|
||||||
<View style={styles.section}>
|
|
||||||
<SectionHeader title="📊 Recent History" />
|
|
||||||
{history.length === 0 ? (
|
|
||||||
<MinimalCard variant="default" style={{ borderRadius: 20 }}>
|
|
||||||
<View style={styles.emptyState}>
|
|
||||||
<Text style={{ fontSize: 64 }}>📍</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
typography.bodyEmphasis,
|
|
||||||
{ color: colors.textPrimary, marginTop: 16 },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
No attendance history yet
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
typography.body,
|
|
||||||
{
|
|
||||||
color: colors.textSecondary,
|
|
||||||
marginTop: 8,
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
Check in to start building your streak! 🔥
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</MinimalCard>
|
|
||||||
) : (
|
|
||||||
<View style={styles.historyList}>
|
|
||||||
{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 (
|
|
||||||
<MinimalCard
|
|
||||||
key={index}
|
|
||||||
variant="bordered"
|
|
||||||
style={{ borderRadius: 16 }}
|
|
||||||
>
|
|
||||||
<View style={styles.historyItem}>
|
|
||||||
<View style={styles.historyLeft}>
|
|
||||||
<IconContainer
|
|
||||||
variant="colored"
|
|
||||||
backgroundColor={
|
|
||||||
checkOut ? `${colors.success}20` : `${colors.info}20`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Ionicons
|
|
||||||
name={checkOut ? "checkmark-done" : "time"}
|
|
||||||
size={20}
|
|
||||||
color={checkOut ? colors.success : colors.info}
|
|
||||||
/>
|
|
||||||
</IconContainer>
|
|
||||||
<View style={{ marginLeft: 12, flex: 1 }}>
|
|
||||||
<Text
|
|
||||||
style={[typography.h3, { color: colors.textPrimary }]}
|
|
||||||
>
|
|
||||||
{checkIn.toLocaleDateString()}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
typography.caption,
|
|
||||||
{ color: colors.textTertiary, marginTop: 2 },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{checkIn.toLocaleTimeString([], {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
{checkOut &&
|
|
||||||
` - ${checkOut.toLocaleTimeString([], {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}`}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{duration && (
|
|
||||||
<Badge
|
|
||||||
label={`${duration}m`}
|
|
||||||
variant="neutral"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</MinimalCard>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Bottom Spacer */}
|
|
||||||
<View style={{ height: 100 }} />
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -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 (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<LinearGradient
|
|
||||||
colors={[theme.colors.white, theme.colors.gray50]}
|
|
||||||
style={[styles.card, theme.shadows.medium]}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<View style={styles.header}>
|
|
||||||
<Text style={styles.title}>Attendance Calendar</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Month Navigation */}
|
|
||||||
<View style={styles.monthNav}>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={goToPreviousMonth}
|
|
||||||
style={styles.navButton}
|
|
||||||
>
|
|
||||||
<Ionicons
|
|
||||||
name="chevron-back"
|
|
||||||
size={20}
|
|
||||||
color={theme.colors.primary}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Text style={styles.monthText}>{monthName}</Text>
|
|
||||||
<TouchableOpacity onPress={goToNextMonth} style={styles.navButton}>
|
|
||||||
<Ionicons
|
|
||||||
name="chevron-forward"
|
|
||||||
size={20}
|
|
||||||
color={theme.colors.primary}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Day Headers */}
|
|
||||||
<View style={styles.dayHeaders}>
|
|
||||||
{["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (
|
|
||||||
<Text key={day} style={styles.dayHeader}>
|
|
||||||
{day}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Calendar Grid */}
|
|
||||||
<View style={styles.calendarGrid}>
|
|
||||||
{calendarDays.map((day, index) => (
|
|
||||||
<View key={index} style={styles.dayCell}>
|
|
||||||
{day.date ? (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.dayContent,
|
|
||||||
day.isToday && styles.todayContent,
|
|
||||||
day.hasAttendance && styles.attendanceContent,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.dayText,
|
|
||||||
day.isToday && styles.todayText,
|
|
||||||
day.hasAttendance && styles.attendanceText,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{day.date.getDate()}
|
|
||||||
</Text>
|
|
||||||
{day.hasAttendance && <View style={styles.attendanceDot} />}
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={styles.emptyCell} />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Legend */}
|
|
||||||
<View style={styles.legend}>
|
|
||||||
<View style={styles.legendItem}>
|
|
||||||
<View style={styles.legendDotAttendance} />
|
|
||||||
<Text style={styles.legendText}>Attended</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.legendItem}>
|
|
||||||
<View style={styles.legendDotToday} />
|
|
||||||
<Text style={styles.legendText}>Today</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</LinearGradient>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -49,8 +49,6 @@ export function CustomTabBar({
|
|||||||
return focused ? "home" : "home-outline";
|
return focused ? "home" : "home-outline";
|
||||||
case "goals":
|
case "goals":
|
||||||
return focused ? "trophy" : "trophy-outline";
|
return focused ? "trophy" : "trophy-outline";
|
||||||
case "attendance":
|
|
||||||
return focused ? "calendar" : "calendar-outline";
|
|
||||||
case "recommendations":
|
case "recommendations":
|
||||||
return focused ? "sparkles" : "sparkles-outline";
|
return focused ? "sparkles" : "sparkles-outline";
|
||||||
case "profile":
|
case "profile":
|
||||||
@ -66,8 +64,6 @@ export function CustomTabBar({
|
|||||||
return "Home";
|
return "Home";
|
||||||
case "goals":
|
case "goals":
|
||||||
return "Goals";
|
return "Goals";
|
||||||
case "attendance":
|
|
||||||
return "Attendance";
|
|
||||||
case "recommendations":
|
case "recommendations":
|
||||||
return "Plans";
|
return "Plans";
|
||||||
case "profile":
|
case "profile":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user