231 lines
4.9 KiB
Vue
231 lines
4.9 KiB
Vue
<template>
|
|
<div class="habits-container">
|
|
<h3>My Habits</h3>
|
|
|
|
<!-- Create Habit Form -->
|
|
<form @submit.prevent="createHabit" class="create-habit-form">
|
|
<h4>Create a New Habit</h4>
|
|
<div v-if="error" class="error-message">{{ error }}</div>
|
|
<input v-model="newHabitName" type="text" placeholder="e.g., Read for 15 minutes" required />
|
|
<div class="days-selector">
|
|
<label v-for="day in dayOptions" :key="day" class="day-label">
|
|
<input type="checkbox" :value="day" v-model="newHabitDays" />
|
|
<span>{{ day }}</span>
|
|
</label>
|
|
</div>
|
|
<button type="submit" :disabled="loading.create">
|
|
{{ loading.create ? 'Adding...' : 'Add Habit' }}
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Habits List -->
|
|
<div class="habits-list">
|
|
<p v-if="loading.fetch">Loading habits...</p>
|
|
<div v-for="habit in habits" :key="habit.id" class="habit-card">
|
|
<div class="habit-info">
|
|
<h4>{{ habit.name }}</h4>
|
|
<div class="habit-days">
|
|
<span v-for="day in habit.daysOfWeek" :key="day" class="day-chip">{{ dayMap[day] }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p v-if="!loading.fetch && habits.length === 0">No habits yet. Add one above!</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
|
|
// --- Type Definitions ---
|
|
interface Habit {
|
|
id: number;
|
|
name: string;
|
|
daysOfWeek: number[]; // Backend returns numbers
|
|
}
|
|
|
|
// --- Composables ---
|
|
const api = useApi();
|
|
|
|
// --- State ---
|
|
const habits = ref<Habit[]>([]);
|
|
const newHabitName = ref('');
|
|
const dayOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
const dayMap: { [key: number]: string } = { 0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun' };
|
|
const newHabitDays = ref<string[]>([]);
|
|
const error = ref<string | null>(null);
|
|
const loading = ref({
|
|
fetch: false,
|
|
create: false,
|
|
});
|
|
|
|
// --- API Functions ---
|
|
const fetchHabits = async () => {
|
|
loading.value.fetch = true;
|
|
error.value = null;
|
|
try {
|
|
habits.value = await api<Habit[]>('/habits');
|
|
} catch (err: any) {
|
|
console.error('Failed to fetch habits:', err);
|
|
error.value = 'Could not load habits.';
|
|
} finally {
|
|
loading.value.fetch = false;
|
|
}
|
|
};
|
|
|
|
const createHabit = async () => {
|
|
if (!newHabitName.value || newHabitDays.value.length === 0) {
|
|
error.value = 'Please provide a name and select at least one day.';
|
|
return;
|
|
}
|
|
loading.value.create = true;
|
|
error.value = null;
|
|
|
|
// Convert day names to numbers
|
|
const dayNumbers = newHabitDays.value.map(dayName => dayOptions.indexOf(dayName));
|
|
|
|
try {
|
|
await api<Habit>('/habits', {
|
|
method: 'POST',
|
|
body: {
|
|
name: newHabitName.value,
|
|
daysOfWeek: dayNumbers,
|
|
},
|
|
});
|
|
|
|
// Clear form and re-fetch the list from the server
|
|
newHabitName.value = '';
|
|
newHabitDays.value = [];
|
|
await fetchHabits();
|
|
|
|
} catch (err: any) {
|
|
console.error('Failed to create habit:', err);
|
|
error.value = err.data?.message || 'Could not create habit.';
|
|
} finally {
|
|
loading.value.create = false;
|
|
}
|
|
};
|
|
|
|
// --- Lifecycle Hooks ---
|
|
onMounted(fetchHabits);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.habits-container {
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
padding-bottom: 40px;
|
|
}
|
|
|
|
h3 {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Create Form */
|
|
.create-habit-form {
|
|
background-color: #fff;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.create-habit-form h4 {
|
|
margin-top: 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.create-habit-form input[type="text"] {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.days-selector {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.day-label {
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.day-label input {
|
|
display: none;
|
|
}
|
|
|
|
.day-label span {
|
|
display: inline-block;
|
|
width: 35px;
|
|
line-height: 35px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 50%;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.day-label input:checked + span {
|
|
background-color: #81a1c1;
|
|
color: white;
|
|
border-color: #81a1c1;
|
|
}
|
|
|
|
.create-habit-form button {
|
|
width: 100%;
|
|
padding: 10px;
|
|
background-color: #5e81ac;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Habits List */
|
|
.habits-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
.habit-info h4 {
|
|
margin: 0 0 10px 0;
|
|
}
|
|
|
|
.habit-days {
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.day-chip {
|
|
background-color: #eceff4;
|
|
color: #4c566a;
|
|
padding: 2px 6px;
|
|
border-radius: 10px;
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
.error-message {
|
|
color: #bf616a;
|
|
background-color: #fbe2e5;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
margin-bottom: 15px;
|
|
text-align: center;
|
|
}
|
|
</style>
|