Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions frontend/src/api/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { API_BASE_URL } from '@/api/config.ts'
import type {DayStatus} from "@/api/dashboard.ts";

export type SingleDayEntry = {
date: string;
dayOfWeek: string;
status: DayStatus;
}

export type HistoryData = {
addictionName: string
singleDayCost: number
totalSavings: number
entries: SingleDayEntry[]
};

export async function fetchHistoryData(token?: string) {
const res = await fetch(`${API_BASE_URL}/api/progress`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
});

if (!res.ok) {
const msg = await res.text();
throw new Error(msg || 'Failed to fetch history data');
}

return await res.json() as HistoryData;
}

export async function updateDayStatus(date: string, status: DayStatus, token?: string) {
const body = { status };

const res = await fetch(`${API_BASE_URL}/api/days/${date}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
},
body: JSON.stringify(body),
});

if (!res.ok) {
const msg = await res.text();
throw new Error(msg || 'Failed to update day status');
}
return await res.json()
}
4 changes: 2 additions & 2 deletions frontend/src/components/HistoryStatusChangeDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { ref, watch } from 'vue';
import Dialog from 'primevue/dialog';
import SelectButton from 'primevue/selectbutton';

type DayStatus = 'success' | 'relapse' | 'none';
type DayStatus = 'success' | 'failure' | 'none';

interface HistoryEntry {
date: Date;
Expand All @@ -61,7 +61,7 @@ const internalEntry = ref<HistoryEntry>({ ...props.entry });
const extendedStatusOptions = ref([
{ label: 'Sukces', value: 'success', icon: 'pi pi-check-circle' },
{ label: 'Brak', value: 'none', icon: 'pi pi-minus-circle' },
{ label: 'Wpadka', value: 'relapse', icon: 'pi pi-exclamation-circle' }
{ label: 'Wpadka', value: 'failure', icon: 'pi pi-exclamation-circle' }
]);

watch(() => props.entry, (newVal) => {
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/LandingLast7Days.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const lastSevenDays = ref<DayUI[]>([]);
const dashboard = useDashboardStore();


async function submit() {
async function loadData() {
try {
const payload: Last7DaysResponse = await dashboard.historyLast7Days();

Expand All @@ -89,8 +89,12 @@ async function submit() {
}
}

defineExpose({
loadData
})

onMounted(() => {
submit();
loadData();
});
</script>

Expand Down
75 changes: 58 additions & 17 deletions frontend/src/components/LandingSummary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
<div class="flex align-items-center justify-content-between mt-2">
<div class="text-5xl font-black italic">{{ streakDays }}</div>
<Button
:icon="streakActive ? 'pi pi-check-circle' : 'pi pi-plus'"
:label="streakActive ? 'Zrobione' : 'Przedłuż'"
:severity="streakActive ? 'success' : 'warn'"
icon="pi pi-pencil"
label="Raportuj"
severity="info"
class="p-button-rounded"
@click="toggleStreak"
@click="openDialog"
/>
</div>
<p class="text-500 text-sm mt-2">Kliknij, aby zatwierdzić dzisiejszy dzień bez
Expand Down Expand Up @@ -69,6 +69,12 @@
</template>
</Card>

<HistoryStatusChangeDialog
v-model:visible="showDialog"
:entry="todayEntry"
@save="saveEntry"
/>

</div>
</div>
</template>
Expand All @@ -79,14 +85,24 @@ import Card from 'primevue/card';
import Button from 'primevue/button';
import Knob from 'primevue/knob';
import {useDashboardStore} from "@/stores/dashboard.ts";
import HistoryStatusChangeDialog from "@/components/HistoryStatusChangeDialog.vue";
import {useHistoryStore} from "@/stores/history.ts";
import type {DayStatus} from "@/api/dashboard.ts";

const savedMoney = ref(0);
const streakDays = ref(0);
const streakActive = ref(false);
const currentDays = ref(0);
const goalDays = ref(10);
const addictionName = ref('uzależnienia');
const showDialog = ref(false);
const todayEntry = ref<{ date: Date, status: DayStatus }>({
date: new Date(),
status: 'none'
});

const dashboard = useDashboardStore()
const history = useHistoryStore()


async function submit() {
try {
Expand All @@ -100,21 +116,46 @@ async function submit() {
}
}

onMounted(() => {
submit();
});
const formatDateToPayload = (date: Date): string => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};


const openDialog = () => {
todayEntry.value = {
date: new Date(),
status: 'none'
};
showDialog.value = true;
};

const emit = defineEmits(['refresh']);

const toggleStreak = () => {
if (!streakActive.value) {
streakDays.value++;
currentDays.value++;
streakActive.value = true;
} else {
streakDays.value--;
currentDays.value--;
streakActive.value = false;
const saveEntry = async (updatedEntry: { date: Date, status: DayStatus | null }) => {
try {
const dateStr = formatDateToPayload(updatedEntry.date);

const statusToSend = updatedEntry.status ?? 'none';

await history.updateStatus(dateStr, statusToSend);

showDialog.value = false;

await submit();
emit('refresh');

} catch (err) {
console.error("Błąd zapisu:", err);
alert("Nie udało się zapisać statusu.");
}
};

onMounted(() => {
submit();
});
</script>

<style scoped>
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/components/RecoveredHoursCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@

<div class="text-section ml-4 flex flex-column">
<span class="label text-sm font-medium opacity-50 uppercase tracking-wide">
Zyskany czas życia
Odzyskaj Życie
</span>
<div class="flex align-items-baseline gap-2">
<span class="value text-4xl font-semibold text-white">5</span>
<span class="unit text-lg opacity-80" style="color: var(--brand-cyan)">godzin</span>
</div>
</div>
</div>
</template>
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/stores/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {defineStore} from 'pinia';
import {
fetchHistoryData,
type HistoryData,
type SingleDayEntry,
updateDayStatus
} from "@/api/history.ts";
import type {DayStatus} from "@/api/dashboard.ts";

export const useHistoryStore = defineStore('history', {
state: () => ({
token: localStorage.getItem('token') as string | null,
}),

getters: {
isAuthenticated: (state) => !!state.token,
},

actions: {
async fetchHistory(): Promise<HistoryData> {
try {
return await fetchHistoryData(this.token ?? undefined);
} catch(e) {
console.error('Failed to fetch history data', e);
throw e;
}
},

async updateStatus(date: string, status: DayStatus): Promise<SingleDayEntry> {
try {
return await updateDayStatus(date, status, this.token ?? undefined);
} catch (e) {
console.error(`Failed to update day status for day ${date}`, e);
throw e;
}
}
}
})


Loading