Skip to content

Commit

Permalink
feat: Add dialogs for starting and stopping sessions at specific time…
Browse files Browse the repository at this point in the history
…s; enhance time handling in session logic
  • Loading branch information
ebanDev committed Jan 14, 2025
1 parent 23ae5da commit 068e886
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 23 deletions.
63 changes: 63 additions & 0 deletions components/dialogs/StartSessionAt.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { kDialog, kDialogButton, kBlock } from 'konsta/vue';
import { useTime } from '~/composables/useTime';
const props = defineProps<{
opened: boolean,
dayStartAt: string
}>();
const emit = defineEmits<{
close: [],
confirm: [date: Date]
}>();
const { getStartOfDay } = useTime(computed(() => props.dayStartAt));
const selectedTime = ref(getStartOfDay(new Date()));
const formatDate = (date: Date) => {
try {
return date.toISOString().slice(0, 16);
} catch (e) {
return new Date().toISOString().slice(0, 16);
}
};
const formattedTime = computed({
get: () => formatDate(selectedTime.value),
set: (value) => {
const date = new Date(value);
if (!isNaN(date.getTime())) {
selectedTime.value = date;
}
}
});
watch(() => props.opened, (newVal) => {
if (newVal) {
const now = new Date();
selectedTime.value = getStartOfDay(now) || now;
}
});
const handleConfirm = () => {
emit('confirm', selectedTime.value);
emit('close');
};
</script>

<template>
<k-dialog :opened="opened" @backdropclick="emit('close')">
<template #title>Démarrer à une heure spécifique</template>

<k-block>
<input type="datetime-local" v-model="formattedTime" class="w-full p-2 rounded-lg">
</k-block>

<template #buttons>
<k-dialog-button @click="emit('close')">Annuler</k-dialog-button>
<k-dialog-button strong @click="handleConfirm">Confirmer</k-dialog-button>
</template>
</k-dialog>
</template>
76 changes: 76 additions & 0 deletions components/dialogs/StopSessionAt.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { kDialog, kDialogButton, kBlock, kButton } from 'konsta/vue';
import { useTime } from '~/composables/useTime';
const props = defineProps<{
opened: boolean,
dayStartAt: string
}>();
const emit = defineEmits<{
close: [],
confirm: [date: Date]
}>();
const { getStartOfDay } = useTime(computed(() => props.dayStartAt));
const selectedTime = ref(getStartOfDay(new Date()));
const formatDate = (date: Date) => {
try {
return date.toISOString().slice(0, 16);
} catch (e) {
return new Date().toISOString().slice(0, 16);
}
};
const formattedTime = computed({
get: () => formatDate(selectedTime.value),
set: (value) => {
const date = new Date(value);
if (!isNaN(date.getTime())) {
selectedTime.value = date;
}
}
});
watch(() => props.opened, (newVal) => {
if (newVal) {
const now = new Date();
selectedTime.value = getStartOfDay(now) || now;
}
});
const handleConfirm = () => {
emit('confirm', selectedTime.value);
emit('close');
};
const adjustTime = (minutes: number) => {
const newTime = new Date();
newTime.setMinutes(newTime.getMinutes() + minutes);
if (!isNaN(newTime.getTime())) {
selectedTime.value = newTime;
}
};
</script>

<template>
<k-dialog :opened="opened" @backdropclick="emit('close')">
<template #title>Arrêter à une heure spécifique</template>

<k-block>
<div class="flex gap-2 mb-2">
<k-button small @click="adjustTime(-10)" class="flex-1">-10min</k-button>
<k-button small @click="adjustTime(-30)" class="flex-1">-30min</k-button>
<k-button small @click="adjustTime(-60)" class="flex-1">-1h</k-button>
</div>
<input type="datetime-local" v-model="formattedTime" class="w-full p-2 rounded-lg">
</k-block>

<template #buttons>
<k-dialog-button @click="emit('close')">Annuler</k-dialog-button>
<k-dialog-button strong @click="handleConfirm">Confirmer</k-dialog-button>
</template>
</k-dialog>
</template>
5 changes: 3 additions & 2 deletions composables/useSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useTime } from './useTime';
import { useSessionGroups } from './useSessionGroups';

export function useSession(wearingSessions, wearingGoal, dayStartAt) {
const { getSessionDay, setToStartOfDay } = useTime(dayStartAt);
const { getSessionDay, setToStartOfDay, getStartOfDay } = useTime(dayStartAt);
const { getTotalTimeWornToday } = useSessionGroups(
wearingSessions,
wearingGoal,
Expand Down Expand Up @@ -39,7 +39,8 @@ export function useSession(wearingSessions, wearingGoal, dayStartAt) {
}
const now = new Date();
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate(), h, m);
if (h < 5 || d > now) d.setDate(d.getDate() - 1);
const startOfDay = getStartOfDay(now);
if (d < startOfDay) d.setDate(d.getDate() + 1);
return d;
};

Expand Down
89 changes: 75 additions & 14 deletions composables/useSessionGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function useSessionGroups(wearingSessions, wearingGoal, dayStartAt, getSe
const addSession = (date, start, end) => {
let group = grouped.find(g => g.date === date);
if (!group) {
group = { date, sessions: [] };
group = { date, sessions: [], isPartialDay: false };
grouped.push(group);
}
group.sessions.push({ start, end });
Expand All @@ -17,30 +17,91 @@ export function useSessionGroups(wearingSessions, wearingGoal, dayStartAt, getSe
wearingSessions.value.forEach(session => {
const start = new Date(session.start);
const end = session.end ? new Date(session.end) : null;

if (end) {
let currentStart = new Date(start), currentEnd = new Date(end);
if (currentEnd < currentStart) currentEnd = new Date(currentEnd.getTime() + 86400000);
while (currentStart < currentEnd) {
const dayStart = getStartOfDay(currentStart);
const nextDay = new Date(dayStart.getTime() + 86400000);
const s = new Date(Math.max(currentStart.getTime(), dayStart.getTime()));
const e = new Date(Math.min(currentEnd.getTime(), nextDay.getTime()));
addSession(getSessionDay(s), s, e);
currentStart = nextDay;
const startDay = getStartOfDay(start);
const endDay = getStartOfDay(end);

if (startDay.getTime() === endDay.getTime()) {
// Same day session
addSession(getSessionDay(start), start, end);
} else {
// Cross-day session, split into multiple days
let currentStart = new Date(start);
let currentEnd = new Date(end);
let isFirstDay = true;

while (currentStart < currentEnd) {
const dayStart = getStartOfDay(currentStart);
const nextDayStart = new Date(dayStart.getTime() + 86400000);
const s = new Date(Math.max(currentStart.getTime(), dayStart.getTime()));
const e = new Date(Math.min(currentEnd.getTime(), nextDayStart.getTime()));

const date = getSessionDay(s);
let group = grouped.find(g => g.date === date);
if (!group) {
group = { date, sessions: [], isPartialDay: !isFirstDay };
grouped.push(group);
} else if (!isFirstDay) {
group.isPartialDay = true;
}
group.sessions.push({ start: s, end: e });

currentStart = nextDayStart;
isFirstDay = false;
}
}
} else {
addSession(getSessionDay(start), start, null);
// Ongoing session
const start = new Date(session.start);
const now = new Date();
const startDay = getStartOfDay(start);
const endDay = getStartOfDay(now);

if (startDay.getTime() === endDay.getTime()) {
// Same day ongoing session
addSession(getSessionDay(start), start, null);
} else {
// Cross-day ongoing session, split into multiple days
let currentStart = new Date(start);
let isFirstDay = true;

while (currentStart <= now) {
const dayStart = getStartOfDay(currentStart);
const nextDayStart = new Date(dayStart.getTime() + 86400000);
const s = new Date(Math.max(currentStart.getTime(), dayStart.getTime()));
const e = new Date(Math.min(now.getTime(), nextDayStart.getTime()));

const date = getSessionDay(s);
let group = grouped.find(g => g.date === date);
if (!group) {
group = { date, sessions: [], isPartialDay: !isFirstDay };
grouped.push(group);
} else if (!isFirstDay) {
group.isPartialDay = true;
}

if (getStartOfDay(e).getTime() === getStartOfDay(now).getTime()) {
// Last day of the ongoing session
group.sessions.push({ start: s, end: null });
} else {
group.sessions.push({ start: s, end: e });
}

currentStart = nextDayStart;
isFirstDay = false;
}
}
}
});

// Compute totals for each group
grouped.forEach(group => {
const baseDay = getStartOfDay(new Date(group.sessions[0].start));
const endOfDay = new Date(baseDay.getTime());
endOfDay.setHours(29, 0, 0, 0);
const endOfDay = new Date(baseDay.getTime() + 86400000);
group.total = group.sessions.reduce((acc, session) => {
const sessionStart = new Date(session.start);
const sessionEnd = session.end ? new Date(session.end) : new Date();
if (sessionEnd < sessionStart) sessionEnd.setTime(sessionEnd.getTime() + 86400000);
const effectiveStart = new Date(Math.max(sessionStart.getTime(), baseDay.getTime()));
const effectiveEnd = new Date(Math.min(sessionEnd.getTime(), endOfDay.getTime()));
const dur = (effectiveEnd - effectiveStart) / 3600000;
Expand Down
12 changes: 7 additions & 5 deletions composables/useTime.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
export function useTime(dayStartAt) {
const setToStartOfDay = d => {
const [h, m] = dayStartAt.value.split(':').map(Number);
d.setHours(h, m, 0, 0);
const [hours, minutes] = typeof dayStartAt.value === 'string'
? dayStartAt.value.split(':').map(Number)
: [Math.floor(dayStartAt.value / 100), dayStartAt.value % 100];
d.setHours(hours, minutes, 0, 0);
};

const getStartOfDay = (d = new Date()) => {
const s = new Date(d);
setToStartOfDay(s);
if (d.getHours() < 5) s.setDate(s.getDate() - 1);
if (s > d) s.setDate(s.getDate() - 1);
return s;
};

const getSessionDay = d => {
const sd = new Date(d);
if (sd.getHours() < 5) sd.setDate(sd.getDate() - 1);
const sd = getStartOfDay(d);
return sd.toLocaleDateString('fr-FR', { day: '2-digit', month: 'long', year: 'numeric' });
};


return { setToStartOfDay, getStartOfDay, getSessionDay };
}
26 changes: 24 additions & 2 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ let timer: NodeJS.Timeout;
const showScoreDialog = ref(false);
const showSessionDialog = ref(false);
const showStartAtDialog = ref(false);
const showStopAtDialog = ref(false);
const currentSessionGroup = ref(null);
const historicalScores = computed(() => getHistoricalScores());
const historicalProgress = computed(() => getHistoricalProgress());
const handleStartAt = (date: Date) => {
startSessionAt(date);
};
const handleStopAt = (date: Date) => {
stopSessionAt(date);
};
onMounted(() => {
const unfinishedSession = wearingSessions.value.find(session => !session.end);
if (unfinishedSession) {
Expand Down Expand Up @@ -125,11 +135,11 @@ const todaySessions = computed(() => {
<Icon name="i-tabler-player-pause" class="mr-2 text-base" />
Arrêter
</k-button>
<k-button v-if="!isWearing" @click="startSessionAt" class="w-full !text-base">
<k-button v-if="!isWearing" @click="showStartAtDialog = true" class="w-full !text-base">
<Icon name="i-tabler-rotate-clockwise" class="mr-2 text-base" />
Démarrer à...
</k-button>
<k-button v-else @click="stopSessionAt" class="w-full !text-base">
<k-button v-else @click="showStopAtDialog = true" class="w-full !text-base">
<Icon name="i-tabler-rotate" class="mr-2 text-base" />
Arrêter à...
</k-button>
Expand Down Expand Up @@ -183,6 +193,18 @@ const todaySessions = computed(() => {
@close="showSessionDialog = false" />
<dialogs-wearing-score :opened="showScoreDialog" :scores="historicalScores" :progress="historicalProgress"
@close="showScoreDialog = false" />
<dialogs-start-session-at
:opened="showStartAtDialog"
:day-start-at="dayStartAt"
@close="showStartAtDialog = false"
@confirm="handleStartAt"
/>
<dialogs-stop-session-at
:opened="showStopAtDialog"
:day-start-at="dayStartAt"
@close="showStopAtDialog = false"
@confirm="handleStopAt"
/>
</main>
</template>

Expand Down

0 comments on commit 068e886

Please sign in to comment.