153 lines
3.6 KiB
Vue
153 lines
3.6 KiB
Vue
<template>
|
|
<div class="dashboard">
|
|
<div v-if="isAuthenticated && user">
|
|
<h2>Today's Habits for {{ user.nickname }}</h2>
|
|
|
|
<div v-if="loading" class="loading-message">
|
|
<p>Loading habits...</p>
|
|
</div>
|
|
|
|
<div v-else-if="todayHabits.length > 0" class="habits-list">
|
|
<div v-for="habit in todayHabits" :key="habit.id" class="habit-card">
|
|
<span class="habit-name">{{ habit.name }}</span>
|
|
<button
|
|
v-if="!isCompleteToday(habit)"
|
|
@click="completeHabit(habit.id)"
|
|
:disabled="completing === habit.id"
|
|
class="complete-btn"
|
|
>
|
|
{{ completing === habit.id ? '...' : 'Complete' }}
|
|
</button>
|
|
<span v-else class="completed-text">✅ Done!</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="empty-state">
|
|
<p>No habits scheduled for today. Great job!</p>
|
|
<NuxtLink to="/habits">Manage Habits</NuxtLink>
|
|
</div>
|
|
|
|
</div>
|
|
<div v-else>
|
|
<p>Loading session...</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue';
|
|
|
|
interface Habit {
|
|
id: number;
|
|
name: string;
|
|
daysOfWeek: number[];
|
|
completions: { id: number; date: string }[];
|
|
}
|
|
|
|
const { user, isAuthenticated } = useAuth();
|
|
const api = useApi();
|
|
|
|
const allHabits = ref<Habit[]>([]);
|
|
const loading = ref(true);
|
|
const completing = ref<number | null>(null);
|
|
|
|
const dayMap = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
const todayIndex = new Date().getDay(); // 0 for Sunday, 1 for Monday...
|
|
|
|
const todayHabits = computed(() => {
|
|
return allHabits.value.filter(h => h.daysOfWeek.includes(todayIndex));
|
|
});
|
|
|
|
const getTodayString = () => new Date().toISOString().split('T')[0];
|
|
|
|
const isCompleteToday = (habit: Habit) => {
|
|
const today = getTodayString();
|
|
return habit.completions.some(c => c.date.startsWith(today));
|
|
};
|
|
|
|
const fetchHabits = async () => {
|
|
loading.value = true;
|
|
try {
|
|
allHabits.value = await api<Habit[]>('/habits');
|
|
} catch (error) {
|
|
console.error("Failed to fetch habits:", error);
|
|
allHabits.value = []; // Clear habits on error
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const completeHabit = async (habitId: number) => {
|
|
completing.value = habitId;
|
|
try {
|
|
await api(`/habits/${habitId}/complete`, { method: 'POST' });
|
|
await fetchHabits(); // Re-fetch the list to update UI
|
|
} catch (error) {
|
|
console.error(`Failed to complete habit ${habitId}:`, error);
|
|
} finally {
|
|
completing.value = null;
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
// We only fetch habits if the user is authenticated.
|
|
// The redirect logic is handled by the middleware/layout now.
|
|
if (isAuthenticated.value) {
|
|
fetchHabits();
|
|
}
|
|
// Watch for auth state changes to fetch habits after login
|
|
watch(isAuthenticated, (isAuth) => {
|
|
if (isAuth && allHabits.value.length === 0) {
|
|
fetchHabits();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dashboard {
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
text-align: center;
|
|
}
|
|
.habits-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
margin-top: 20px;
|
|
}
|
|
.habit-card {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px;
|
|
background-color: #fff;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
text-align: left;
|
|
}
|
|
.habit-name {
|
|
font-size: 1.1em;
|
|
}
|
|
.complete-btn {
|
|
padding: 8px 12px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
background-color: #88c0d0;
|
|
color: #2e3440;
|
|
cursor: pointer;
|
|
}
|
|
.completed-text {
|
|
color: #a3be8c;
|
|
font-weight: bold;
|
|
}
|
|
.empty-state {
|
|
margin-top: 40px;
|
|
color: #666;
|
|
}
|
|
.empty-state a {
|
|
margin-top: 10px;
|
|
display: inline-block;
|
|
}
|
|
</style>
|