refactor: format
TS Lint / ESLint (push) Has been cancelled
TS Lint / Oxlint (push) Has been cancelled
TS Lint / TypeScript (push) Has been cancelled

This commit is contained in:
2026-07-14 11:47:05 +02:00
parent 566f8403ef
commit a9b2e31d81
17 changed files with 432 additions and 183 deletions
+21 -6
View File
@@ -1,8 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, defineAsyncComponent } from "vue"; import { ref, onMounted, defineAsyncComponent } from "vue";
const TaskList = defineAsyncComponent(() => import("@/components/TaskList.vue")); const TaskList = defineAsyncComponent(
const PageFooter = defineAsyncComponent(() => import("@/components/PageFooter.vue")); () => import("@/components/TaskList.vue"),
const AlertsContainer = defineAsyncComponent(() => import("@/components/AlertsContainer.vue")); );
const PageFooter = defineAsyncComponent(
() => import("@/components/PageFooter.vue"),
);
const AlertsContainer = defineAsyncComponent(
() => import("@/components/AlertsContainer.vue"),
);
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
@@ -14,11 +20,20 @@ onMounted(() => {
</script> </script>
<template> <template>
<main :style="{ display: visible ? 'flex' : 'none' }" class="min-h-screen flex-col"> <main
:style="{ display: visible ? 'flex' : 'none' }"
class="min-h-screen flex-col"
>
<div class="hero bg-base-200 grow"> <div class="hero bg-base-200 grow">
<div class="hero-content text-center"> <div class="hero-content text-center">
<div class="max-w-md min-w-96 bg-base-100 rounded-box shadow-md"> <div
<h1 class="p-4 pb-2 text-4xl font-bold opacity-60 tracking-wide select-none">Tout Doux</h1> class="max-w-md min-w-96 bg-base-100 rounded-box shadow-md"
>
<h1
class="p-4 pb-2 text-4xl font-bold opacity-60 tracking-wide select-none"
>
Tout Doux
</h1>
<task-list /> <task-list />
</div> </div>
</div> </div>
-1
View File
@@ -1,7 +1,6 @@
import { parseTask } from "@/lib/tasks"; import { parseTask } from "@/lib/tasks";
import type { Task, TaskCreateData, TaskUpdateData } from "@/types"; import type { Task, TaskCreateData, TaskUpdateData } from "@/types";
export async function getTasks(list: string): Promise<Task[]> { export async function getTasks(list: string): Promise<Task[]> {
const response = await fetch(`/api/lists/${list}/tasks`); const response = await fetch(`/api/lists/${list}/tasks`);
return (await response.json()).map(parseTask); return (await response.json()).map(parseTask);
+24 -9
View File
@@ -1,12 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Alert } from '@/types'; import type { Alert } from "@/types";
import { computed, ref, onBeforeMount } from 'vue'; import { computed, ref, onBeforeMount } from "vue";
import { CircleX, CircleAlert, CircleCheck, Info } from '@lucide/vue'; import { CircleX, CircleAlert, CircleCheck, Info } from "@lucide/vue";
const props = defineProps<{ alert: Alert }>(); const props = defineProps<{ alert: Alert }>();
const dismissed = ref<boolean>(false); const dismissed = ref<boolean>(false);
const refreshKey = ref<number>(1); const refreshKey = ref<number>(1);
const visible = computed<boolean>(() => refreshKey.value > 0 && !dismissed.value && (new Date().getTime() - props.alert.created.getTime()) <= props.alert.seconds * 1000); const visible = computed<boolean>(
() =>
refreshKey.value > 0 &&
!dismissed.value &&
new Date().getTime() - props.alert.created.getTime() <=
props.alert.seconds * 1000,
);
function onDismiss() { function onDismiss() {
dismissed.value = true; dismissed.value = true;
@@ -20,11 +26,20 @@ onBeforeMount(() => {
</script> </script>
<template> <template>
<div v-if="visible" class="alert alert-info alert-soft cursor-pointer" :class="`alert-${alert.type}`" @click="onDismiss"> <div
<CircleX v-if="alert.type === 'error'" /> <!-- alert-error --> v-if="visible"
<CircleAlert v-if="alert.type === 'warning'" /> <!-- alert-warning --> class="alert alert-info alert-soft cursor-pointer"
<CircleCheck v-if="alert.type === 'success'" /> <!-- alert-success --> :class="`alert-${alert.type}`"
<Info v-if="alert.type === 'info'" /> <!-- alert-info --> @click="onDismiss"
>
<CircleX v-if="alert.type === 'error'" />
<!-- alert-error -->
<CircleAlert v-if="alert.type === 'warning'" />
<!-- alert-warning -->
<CircleCheck v-if="alert.type === 'success'" />
<!-- alert-success -->
<Info v-if="alert.type === 'info'" />
<!-- alert-info -->
<span>{{ alert.message }}</span> <span>{{ alert.message }}</span>
</div> </div>
</template> </template>
+10 -4
View File
@@ -1,13 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAlertStore } from '@/stores/alerts'; import { useAlertStore } from "@/stores/alerts";
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from "vue";
const AlertItem = defineAsyncComponent(() => import("@/components/AlertItem.vue")); const AlertItem = defineAsyncComponent(
() => import("@/components/AlertItem.vue"),
);
const { alerts } = useAlertStore(); const { alerts } = useAlertStore();
</script> </script>
<template> <template>
<div class="toast select-none"> <div class="toast select-none">
<alert-item v-for="alert in alerts" :key="alert.created.getTime()" :alert="alert" /> <alert-item
v-for="alert in alerts"
:key="alert.created.getTime()"
:alert="alert"
/>
</div> </div>
</template> </template>
+87 -15
View File
@@ -63,25 +63,97 @@ watch(cron, () => {
<template> <template>
<div class="flex gap-2"> <div class="flex gap-2">
<select v-model="cronType" class="select" @change="onChangeCronType"> <select v-model="cronType" class="select" @change="onChangeCronType">
<option :value="CronType.ONE_TIME">{{ $t('cron.type.one_time') }}</option> <option :value="CronType.ONE_TIME">
<option :value="CronType.EVERY_DAY">{{ $t('cron.type.every_day') }}</option> {{ $t("cron.type.one_time") }}
<option :value="CronType.EVERY_WEEK">{{ $t('cron.type.every_week') }}</option> </option>
<option :value="CronType.EVERY_MONTH">{{ $t('cron.type.every_month') }}</option> <option :value="CronType.EVERY_DAY">
<option :value="CronType.EVERY_YEAR">{{ $t('cron.type.every_year') }}</option> {{ $t("cron.type.every_day") }}
<option :value="CronType.SPECIFIC">{{ $t('cron.type.specific') }}</option> </option>
<option :value="CronType.EVERY_WEEK">
{{ $t("cron.type.every_week") }}
</option>
<option :value="CronType.EVERY_MONTH">
{{ $t("cron.type.every_month") }}
</option>
<option :value="CronType.EVERY_YEAR">
{{ $t("cron.type.every_year") }}
</option>
<option :value="CronType.SPECIFIC">
{{ $t("cron.type.specific") }}
</option>
</select> </select>
<select v-if="cronType === CronType.EVERY_DAY" v-model="cronValue" class="select" @change="onChangeCronValue"> <select
<option v-for="i in Array.from(Array(24).keys())" :key="`h${i}`" :value="i">{{ i.toFixed(0).padStart(2, '0') }}:00</option> v-if="cronType === CronType.EVERY_DAY"
v-model="cronValue"
class="select"
@change="onChangeCronValue"
>
<option
v-for="i in Array.from(Array(24).keys())"
:key="`h${i}`"
:value="i"
>
{{ i.toFixed(0).padStart(2, "0") }}:00
</option>
</select> </select>
<select v-if="cronType === CronType.EVERY_WEEK" v-model="cronValue" class="select" @change="onChangeCronValue"> <select
<option v-for="i in Array.from(Array(7).keys())" :key="`w${i}`" :value="i + 1">{{ $t(`cron.week.${i.toFixed(0)}`) }}</option> v-if="cronType === CronType.EVERY_WEEK"
v-model="cronValue"
class="select"
@change="onChangeCronValue"
>
<option
v-for="i in Array.from(Array(7).keys())"
:key="`w${i}`"
:value="i + 1"
>
{{ $t(`cron.week.${i.toFixed(0)}`) }}
</option>
</select> </select>
<select v-if="cronType === CronType.EVERY_MONTH" v-model="cronValue" class="select" @change="onChangeCronValue"> <select
<option v-for="i in Array.from(Array(31).keys())" :key="`d${i}`" :value="i + 1">{{i + 1}}{{ i % 10 == 0 ? $t('cron.month.st') : (i % 10 === 1 ? $t('cron.month.nd'): (i % 10 === 2 ? $t('cron.month.rd') : $t('cron.month.th'))) }}</option> v-if="cronType === CronType.EVERY_MONTH"
v-model="cronValue"
class="select"
@change="onChangeCronValue"
>
<option
v-for="i in Array.from(Array(31).keys())"
:key="`d${i}`"
:value="i + 1"
>
{{ i + 1
}}{{
i % 10 == 0
? $t("cron.month.st")
: i % 10 === 1
? $t("cron.month.nd")
: i % 10 === 2
? $t("cron.month.rd")
: $t("cron.month.th")
}}
</option>
</select> </select>
<select v-if="cronType === CronType.EVERY_YEAR" v-model="cronValue" class="select" @change="onChangeCronValue"> <select
<option v-for="i in Array.from(Array(12).keys())" :key="`m${i}`" :value="i + 1">{{ $t(`cron.year.${i.toFixed(0)}`) }}</option> v-if="cronType === CronType.EVERY_YEAR"
v-model="cronValue"
class="select"
@change="onChangeCronValue"
>
<option
v-for="i in Array.from(Array(12).keys())"
:key="`m${i}`"
:value="i + 1"
>
{{ $t(`cron.year.${i.toFixed(0)}`) }}
</option>
</select> </select>
<input v-if="cronType === CronType.SPECIFIC" v-model="rawCron" type="text" class="input font-mono" :class="valid ? '' : 'input-error'" @input="onInputRawCron" /> <input
v-if="cronType === CronType.SPECIFIC"
v-model="rawCron"
type="text"
class="input font-mono"
:class="valid ? '' : 'input-error'"
@input="onInputRawCron"
/>
</div> </div>
</template> </template>
+5 -4
View File
@@ -1,15 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { setCookie } from '@/lib/cookies'; import { setCookie } from "@/lib/cookies";
import { useI18n } from 'vue-i18n'; import { useI18n } from "vue-i18n";
const i18n = useI18n(); const i18n = useI18n();
function onSetLang(lang: string) { function onSetLang(lang: string) {
i18n.locale.value = lang; i18n.locale.value = lang;
setCookie('lang', lang); setCookie("lang", lang);
} }
</script> </script>
<template> <template>
<a href="#" @click.prevent="onSetLang('fr')">🇫🇷</a> / <a href="#" @click.prevent="onSetLang('en')">🇺🇸</a> <a href="#" @click.prevent="onSetLang('fr')">🇫🇷</a> /
<a href="#" @click.prevent="onSetLang('en')">🇺🇸</a>
</template> </template>
+8 -4
View File
@@ -1,18 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineAsyncComponent } from "vue"; import { defineAsyncComponent } from "vue";
const LangInput = defineAsyncComponent(() => import("@/components/LangInput.vue")); const LangInput = defineAsyncComponent(
() => import("@/components/LangInput.vue"),
);
</script> </script>
<template> <template>
<footer class="footer sm:footer-horizontal footer-center bg-base-300 text-base-content p-2"> <footer
class="footer sm:footer-horizontal footer-center bg-base-300 text-base-content p-2"
>
<aside> <aside>
<p> <p>
<lang-input /> - 2026 - <lang-input /> - 2026 -
<a <a
href="https://git.klemek.fr/klemek/tout-doux" href="https://git.klemek.fr/klemek/tout-doux"
class="underline"> class="underline"
>
klemek klemek
</a> </a>
</p> </p>
+47 -20
View File
@@ -3,19 +3,21 @@ import { onBeforeMount, ref, watch, computed, defineAsyncComponent } from "vue";
import type { Task } from "@/types"; import type { Task } from "@/types";
import { Copy, Trash, Save, X } from "@lucide/vue"; import { Copy, Trash, Save, X } from "@lucide/vue";
import { updateTask } from "@/api/tasks"; import { updateTask } from "@/api/tasks";
const CronInput = defineAsyncComponent(() => import("@/components/CronInput.vue")); const CronInput = defineAsyncComponent(
() => import("@/components/CronInput.vue"),
);
import { taskCompleted, taskNextReset } from "@/lib/tasks"; import { taskCompleted, taskNextReset } from "@/lib/tasks";
import { relativeTime } from "@/lib/dates"; import { relativeTime } from "@/lib/dates";
import { useAlertStore } from "@/stores/alerts"; import { useAlertStore } from "@/stores/alerts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const emit = defineEmits<(e: 'clone' | 'delete', task: Task) => void>(); const emit = defineEmits<(e: "clone" | "delete", task: Task) => void>();
const task = defineModel<Task>({ required: true }); const task = defineModel<Task>({ required: true });
defineProps<{ showLastChecked: boolean; showNextReset: boolean }>(); defineProps<{ showLastChecked: boolean; showNextReset: boolean }>();
const editMode = ref<boolean>(false); const editMode = ref<boolean>(false);
const editName = ref<string>(''); const editName = ref<string>("");
const editCron = ref<string | null>(null); const editCron = ref<string | null>(null);
const refreshKey = ref<number>(0); const refreshKey = ref<number>(0);
@@ -31,13 +33,17 @@ function onClose() {
} }
function onSave() { function onSave() {
updateTask(task.value.id, { name: editName.value, reset_cron: editCron.value }) updateTask(task.value.id, {
name: editName.value,
reset_cron: editCron.value,
})
.then((newTask) => { .then((newTask) => {
task.value = newTask; task.value = newTask;
alertSuccess(i18n.t('alerts.task_updated')); alertSuccess(i18n.t("alerts.task_updated"));
}).catch(() => {
alertError(i18n.t('alerts.task_update_error'))
}) })
.catch(() => {
alertError(i18n.t("alerts.task_update_error"));
});
task.value.name = editName.value; task.value.name = editName.value;
task.value.reset_cron = editCron.value; task.value.reset_cron = editCron.value;
editMode.value = false; editMode.value = false;
@@ -45,28 +51,36 @@ function onSave() {
function onCheck() { function onCheck() {
if (!checked.value) { if (!checked.value) {
updateTask(task.value.id, { check_date: (new Date()).toISOString(), previous_check_date: task.value.check_date?.toISOString() }) updateTask(task.value.id, {
check_date: new Date().toISOString(),
previous_check_date: task.value.check_date?.toISOString(),
})
.then((newTask) => { .then((newTask) => {
task.value = newTask; task.value = newTask;
}).catch(() => { })
alertError(i18n.t('alerts.task_update_error')) .catch(() => {
alertError(i18n.t("alerts.task_update_error"));
}); });
} else { } else {
updateTask(task.value.id, { check_date: null, previous_check_date: task.value.check_date?.toISOString() }) updateTask(task.value.id, {
check_date: null,
previous_check_date: task.value.check_date?.toISOString(),
})
.then((newTask) => { .then((newTask) => {
task.value = newTask; task.value = newTask;
}).catch(() => { })
alertError(i18n.t('alerts.task_update_error')) .catch(() => {
alertError(i18n.t("alerts.task_update_error"));
}); });
} }
} }
function onClone() { function onClone() {
emit('clone', task.value); emit("clone", task.value);
} }
function onDelete() { function onDelete() {
emit('delete', task.value); emit("delete", task.value);
} }
onBeforeMount(() => { onBeforeMount(() => {
@@ -89,7 +103,13 @@ watch(task, () => {
<template> <template>
<div class="list-row text-left"> <div class="list-row text-left">
<div class="content-center"> <div class="content-center">
<input :id="refreshKey.toFixed(0)" type="checkbox" class="checkbox" :checked="checked" @click.prevent="onCheck" /> <input
:id="refreshKey.toFixed(0)"
type="checkbox"
class="checkbox"
:checked="checked"
@click.prevent="onCheck"
/>
</div> </div>
<div <div
v-if="!editMode" v-if="!editMode"
@@ -97,11 +117,18 @@ watch(task, () => {
@click.prevent="editMode = true" @click.prevent="editMode = true"
> >
<div>{{ task.name }}</div> <div>{{ task.name }}</div>
<div v-if="showLastChecked && !checked && task.check_date" class="font-light text-xs italic"> <div
{{ $t('checked') }} {{ relativeTime(task.check_date, $i18n.locale) }} v-if="showLastChecked && !checked && task.check_date"
class="font-light text-xs italic"
>
{{ $t("checked") }}
{{ relativeTime(task.check_date, $i18n.locale) }}
</div> </div>
<div v-if="showNextReset && checked && nextReset" class="font-light text-xs italic"> <div
{{ $t('resets') }} {{ relativeTime(nextReset, $i18n.locale) }} v-if="showNextReset && checked && nextReset"
class="font-light text-xs italic"
>
{{ $t("resets") }} {{ relativeTime(nextReset, $i18n.locale) }}
</div> </div>
</div> </div>
<template v-else> <template v-else>
+88 -39
View File
@@ -3,7 +3,9 @@ import { ref, onBeforeMount, computed, defineAsyncComponent } from "vue";
import type { Task } from "@/types"; import type { Task } from "@/types";
import { getTasks, createTask, deleteTask } from "@/api/tasks"; import { getTasks, createTask, deleteTask } from "@/api/tasks";
import { CirclePlus } from "@lucide/vue"; import { CirclePlus } from "@lucide/vue";
const TaskItem = defineAsyncComponent(() => import("@/components/TaskItem.vue")); const TaskItem = defineAsyncComponent(
() => import("@/components/TaskItem.vue"),
);
import { taskCompleted, sortTasks } from "@/lib/tasks"; import { taskCompleted, sortTasks } from "@/lib/tasks";
import { SortType } from "@/enums"; import { SortType } from "@/enums";
import { booleanCookieRef, enumCookieRef } from "@/lib/cookies"; import { booleanCookieRef, enumCookieRef } from "@/lib/cookies";
@@ -14,7 +16,10 @@ const taskInput = ref<string>("");
const tasks = ref<Task[]>([]); const tasks = ref<Task[]>([]);
const list = ref<string>("test"); const list = ref<string>("test");
const sortType = enumCookieRef<SortType>("toutDouxSortType", SortType.CREATED_AT); const sortType = enumCookieRef<SortType>(
"toutDouxSortType",
SortType.CREATED_AT,
);
const sortReverse = booleanCookieRef("toutDouxSortReverse", false); const sortReverse = booleanCookieRef("toutDouxSortReverse", false);
const showLastChecked = booleanCookieRef("toutDouxShowLastChecked", false); const showLastChecked = booleanCookieRef("toutDouxShowLastChecked", false);
const showNextReset = booleanCookieRef("toutDouxShowNextReset", false); const showNextReset = booleanCookieRef("toutDouxShowNextReset", false);
@@ -34,13 +39,14 @@ function fetchTasks() {
tasks.value = data; tasks.value = data;
reSortTasks(); reSortTasks();
setTimeout(fetchTasks, 10000); setTimeout(fetchTasks, 10000);
}).catch(() => {
if (tasks.value.length !== 0) {
alertWarning(i18n.t('alerts.task_refresh_error'));
} else {
alertError(i18n.t('alerts.task_fetch_error'));
}
}) })
.catch(() => {
if (tasks.value.length !== 0) {
alertWarning(i18n.t("alerts.task_refresh_error"));
} else {
alertError(i18n.t("alerts.task_fetch_error"));
}
});
} }
function reSortTasks() { function reSortTasks() {
@@ -56,10 +62,10 @@ function onNewTask() {
.then((data: Task) => { .then((data: Task) => {
tasks.value.push(data); tasks.value.push(data);
reSortTasks(); reSortTasks();
alertSuccess(i18n.t('alerts.task_created')); alertSuccess(i18n.t("alerts.task_created"));
}) })
.catch(() => { .catch(() => {
alertError(i18n.t('alerts.task_create_error')) alertError(i18n.t("alerts.task_create_error"));
}) })
.finally(() => { .finally(() => {
taskInput.value = ""; taskInput.value = "";
@@ -71,21 +77,24 @@ function onCloneTask(task: Task) {
createTask(list.value, { createTask(list.value, {
name: task.name, name: task.name,
reset_cron: task.reset_cron, reset_cron: task.reset_cron,
}).then((data: Task) => {
tasks.value.push(data);
alertSuccess(i18n.t('alerts.task_cloned'));
}).catch(() => {
alertError(i18n.t('alerts.task_create_error'))
}) })
.then((data: Task) => {
tasks.value.push(data);
alertSuccess(i18n.t("alerts.task_cloned"));
})
.catch(() => {
alertError(i18n.t("alerts.task_create_error"));
});
} }
function onDeleteTask(task: Task) { function onDeleteTask(task: Task) {
deleteTask(task.id) deleteTask(task.id)
.then(() => { .then(() => {
alertSuccess(i18n.t('alerts.task_deleted')); alertSuccess(i18n.t("alerts.task_deleted"));
}).catch(() => {
alertError(i18n.t('alerts.task_delete_error'))
}) })
.catch(() => {
alertError(i18n.t("alerts.task_delete_error"));
});
tasks.value.splice(tasks.value.indexOf(task), 1); tasks.value.splice(tasks.value.indexOf(task), 1);
} }
@@ -115,10 +124,13 @@ onBeforeMount(fetchTasks);
</li> </li>
</template> </template>
<li v-if="empty" class="list-row italic text-xl font-extralight"> <li v-if="empty" class="list-row italic text-xl font-extralight">
{{ $t('empty_hint') }} {{ $t("empty_hint") }}
</li> </li>
<li v-if="allCompleted && separateCompleted" class="list-row italic text-xl font-extralight"> <li
{{ $t('all_completed_hint') }} v-if="allCompleted && separateCompleted"
class="list-row italic text-xl font-extralight"
>
{{ $t("all_completed_hint") }}
</li> </li>
<li class="list-row"> <li class="list-row">
<div class="list-col-grow"> <div class="list-col-grow">
@@ -141,12 +153,18 @@ onBeforeMount(fetchTasks);
</div> </div>
</li> </li>
</ul> </ul>
<div v-if="separateCompleted && hasCompleted" class="collapse collapse-arrow"> <div
v-if="separateCompleted && hasCompleted"
class="collapse collapse-arrow"
>
<input id="completed-dropdown" type="checkbox" /> <input id="completed-dropdown" type="checkbox" />
<div class="collapse-title mb-0">{{ $t('completed') }}</div> <div class="collapse-title mb-0">{{ $t("completed") }}</div>
<div class="collapse-content p-0"> <div class="collapse-content p-0">
<ul class="list"> <ul class="list">
<template v-for="(task, i) in tasks" :key="`${task.id}-completed`"> <template
v-for="(task, i) in tasks"
:key="`${task.id}-completed`"
>
<li v-if="tasks[i] && taskCompleted(task)"> <li v-if="tasks[i] && taskCompleted(task)">
<task-item <task-item
v-model="tasks[i]" v-model="tasks[i]"
@@ -162,34 +180,65 @@ onBeforeMount(fetchTasks);
</div> </div>
<div class="collapse collapse-arrow font-light text-sm"> <div class="collapse collapse-arrow font-light text-sm">
<input id="options-dropdown" type="checkbox" /> <input id="options-dropdown" type="checkbox" />
<div class="collapse-title">{{ $t('options.title') }}</div> <div class="collapse-title">{{ $t("options.title") }}</div>
<div class="collapse-content text-sm"> <div class="collapse-content text-sm">
<fieldset class="fieldset flex flex-col gap-2"> <fieldset class="fieldset flex flex-col gap-2">
<label class="label"> <label class="label">
Sort by Sort by
<select v-model="sortType" class="select" @change="onChangeSortType"> <select
<option :value="SortType.CREATED_AT">{{ $t('options.sort.created_at') }}</option> v-model="sortType"
<option :value="SortType.UPDATED_AT">{{ $t('options.sort.updated_at') }}</option> class="select"
<option :value="SortType.CHECK_DATE">{{ $t('options.sort.check_date') }}</option> @change="onChangeSortType"
<option :value="SortType.RESET_DATE">{{ $t('options.sort.reset_date') }}</option> >
<option :value="SortType.NAME">{{ $t('options.sort.name') }}</option> <option :value="SortType.CREATED_AT">
{{ $t("options.sort.created_at") }}
</option>
<option :value="SortType.UPDATED_AT">
{{ $t("options.sort.updated_at") }}
</option>
<option :value="SortType.CHECK_DATE">
{{ $t("options.sort.check_date") }}
</option>
<option :value="SortType.RESET_DATE">
{{ $t("options.sort.reset_date") }}
</option>
<option :value="SortType.NAME">
{{ $t("options.sort.name") }}
</option>
</select> </select>
</label> </label>
<label class="label"> <label class="label">
<input v-model="sortReverse" type="checkbox" class="checkbox" @change="onChangeSortReverse" /> <input
{{ $t('options.reverse_sort') }} v-model="sortReverse"
type="checkbox"
class="checkbox"
@change="onChangeSortReverse"
/>
{{ $t("options.reverse_sort") }}
</label> </label>
<label class="label"> <label class="label">
<input v-model="showLastChecked" type="checkbox" class="checkbox" /> <input
{{ $t('options.last_checked') }} v-model="showLastChecked"
type="checkbox"
class="checkbox"
/>
{{ $t("options.last_checked") }}
</label> </label>
<label class="label"> <label class="label">
<input v-model="showNextReset" type="checkbox" class="checkbox" /> <input
{{ $t('options.next_reset') }} v-model="showNextReset"
type="checkbox"
class="checkbox"
/>
{{ $t("options.next_reset") }}
</label> </label>
<label class="label"> <label class="label">
<input v-model="separateCompleted" type="checkbox" class="checkbox" /> <input
{{ $t('options.separate_completed') }} v-model="separateCompleted"
type="checkbox"
class="checkbox"
/>
{{ $t("options.separate_completed") }}
</label> </label>
</fieldset> </fieldset>
</div> </div>
+1 -1
View File
@@ -1 +1 @@
export const DEFAULT_CRON = '0 0 * * *'; export const DEFAULT_CRON = "0 0 * * *";
+12 -2
View File
@@ -44,9 +44,19 @@ export function cookieRef<T>(
} }
export function booleanCookieRef(name: string, defaultValue: boolean) { export function booleanCookieRef(name: string, defaultValue: boolean) {
return cookieRef<boolean>(name, defaultValue, (v) => v === "1", (v) => v ? "1" : "0"); return cookieRef<boolean>(
name,
defaultValue,
(v) => v === "1",
(v) => (v ? "1" : "0"),
);
} }
export function enumCookieRef<T extends number>(name: string, defaultValue: T) { export function enumCookieRef<T extends number>(name: string, defaultValue: T) {
return cookieRef<T>(name, defaultValue, (v) => parseInt(v) as T, (v: T) => v.toFixed(0)); return cookieRef<T>(
name,
defaultValue,
(v) => parseInt(v) as T,
(v: T) => v.toFixed(0),
);
} }
+22 -16
View File
@@ -1,48 +1,54 @@
import { CronType } from "@/enums"; import { CronType } from "@/enums";
import { CronExpressionParser } from "cron-parser"; import { CronExpressionParser } from "cron-parser";
export function parseCron(cron: string | null): { type: CronType, value: number } { export function parseCron(cron: string | null): {
type: CronType;
value: number;
} {
let value: RegExpMatchArray | null; let value: RegExpMatchArray | null;
if (cron === null) { if (cron === null) {
return { return {
type: CronType.ONE_TIME, type: CronType.ONE_TIME,
value: 0 value: 0,
} };
} else if ((value = /^0 (\d+) \* \* \*$/i.exec(cron)) !== null) { } else if ((value = /^0 (\d+) \* \* \*$/i.exec(cron)) !== null) {
return { return {
type: CronType.EVERY_DAY, type: CronType.EVERY_DAY,
value: parseInt(value[1] ?? '') value: parseInt(value[1] ?? ""),
} };
} else if ((value = /^0 0 \* \* (\d+)$/i.exec(cron)) !== null) { } else if ((value = /^0 0 \* \* (\d+)$/i.exec(cron)) !== null) {
return { return {
type: CronType.EVERY_WEEK, type: CronType.EVERY_WEEK,
value: parseInt(value[1] ?? '') value: parseInt(value[1] ?? ""),
} };
} else if ((value = /^0 0 (\d+) \* \*$/i.exec(cron)) !== null) { } else if ((value = /^0 0 (\d+) \* \*$/i.exec(cron)) !== null) {
return { return {
type: CronType.EVERY_MONTH, type: CronType.EVERY_MONTH,
value: parseInt(value[1] ?? '') value: parseInt(value[1] ?? ""),
} };
} else if ((value = /^0 0 1 (\d+) \*$/i.exec(cron)) !== null) { } else if ((value = /^0 0 1 (\d+) \*$/i.exec(cron)) !== null) {
return { return {
type: CronType.EVERY_YEAR, type: CronType.EVERY_YEAR,
value: parseInt(value[1] ?? '') value: parseInt(value[1] ?? ""),
} };
} else { } else {
return { return {
type: CronType.SPECIFIC, type: CronType.SPECIFIC,
value: 0 value: 0,
} };
} }
} }
export function getCron(type: CronType, value: number, fallback: string): string | null { export function getCron(
type: CronType,
value: number,
fallback: string,
): string | null {
switch (type) { switch (type) {
case CronType.EVERY_DAY: case CronType.EVERY_DAY:
return `0 ${value.toFixed(0)} * * *`; return `0 ${value.toFixed(0)} * * *`;
case CronType.EVERY_WEEK: case CronType.EVERY_WEEK:
return `0 0 * * ${value.toFixed(0)}` return `0 0 * * ${value.toFixed(0)}`;
case CronType.EVERY_MONTH: case CronType.EVERY_MONTH:
return `0 0 ${value.toFixed(0)} * *`; return `0 0 ${value.toFixed(0)} * *`;
case CronType.EVERY_YEAR: case CronType.EVERY_YEAR:
+6 -2
View File
@@ -1,6 +1,10 @@
import { formatDistanceToNow } from "date-fns"; import { formatDistanceToNow } from "date-fns";
import { enUS, fr } from 'date-fns/locale'; import { enUS, fr } from "date-fns/locale";
export function relativeTime(date: Date, locale: string): string { export function relativeTime(date: Date, locale: string): string {
return formatDistanceToNow(date, {addSuffix: true, includeSeconds: false, locale: locale === 'fr' ? fr : enUS}); return formatDistanceToNow(date, {
addSuffix: true,
includeSeconds: false,
locale: locale === "fr" ? fr : enUS,
});
} }
+45 -8
View File
@@ -21,7 +21,9 @@ export function taskNextReset(task: Task): Date | null {
return null; return null;
} }
try { try {
const interval = CronExpressionParser.parse(task.reset_cron, {currentDate: task.check_date}); const interval = CronExpressionParser.parse(task.reset_cron, {
currentDate: task.check_date,
});
return interval.next().toDate(); return interval.next().toDate();
} catch { } catch {
return null; return null;
@@ -33,21 +35,56 @@ export function taskCompleted(task: Task): boolean {
if (nextReset === null) { if (nextReset === null) {
return task.check_date !== null; return task.check_date !== null;
} }
return nextReset.getTime() > (new Date()).getTime(); return nextReset.getTime() > new Date().getTime();
} }
export function sortTasks(tasks: Task[], sortType: SortType, reverse: boolean): Task[] { export function sortTasks(
tasks: Task[],
sortType: SortType,
reverse: boolean,
): Task[] {
switch (sortType) { switch (sortType) {
case SortType.CREATED_AT: case SortType.CREATED_AT:
return tasks.slice().sort((a: Task, b: Task) => (reverse ? -1 : 1) * (a.created_at.getTime() - b.created_at.getTime())); return tasks
.slice()
.sort(
(a: Task, b: Task) =>
(reverse ? -1 : 1) *
(a.created_at.getTime() - b.created_at.getTime()),
);
case SortType.UPDATED_AT: case SortType.UPDATED_AT:
return tasks.slice().sort((a: Task, b: Task) => (reverse ? -1 : 1) * (a.updated_at.getTime() - b.updated_at.getTime())); return tasks
.slice()
.sort(
(a: Task, b: Task) =>
(reverse ? -1 : 1) *
(a.updated_at.getTime() - b.updated_at.getTime()),
);
case SortType.CHECK_DATE: case SortType.CHECK_DATE:
return tasks.slice().sort((a: Task, b: Task) => (reverse ? -1 : 1) * ((a.check_date ?? a.created_at).getTime() - (b.check_date ?? b.created_at).getTime())); return tasks
.slice()
.sort(
(a: Task, b: Task) =>
(reverse ? -1 : 1) *
((a.check_date ?? a.created_at).getTime() -
(b.check_date ?? b.created_at).getTime()),
);
case SortType.RESET_DATE: case SortType.RESET_DATE:
return tasks.slice().sort((a: Task, b: Task) => (reverse ? -1 : 1) * (taskNextReset(a) ?? a.created_at).getTime() - (taskNextReset(b) ?? b.created_at).getTime()); return tasks
.slice()
.sort(
(a: Task, b: Task) =>
(reverse ? -1 : 1) *
(taskNextReset(a) ?? a.created_at).getTime() -
(taskNextReset(b) ?? b.created_at).getTime(),
);
case SortType.NAME: case SortType.NAME:
return tasks.slice().sort((a: Task, b: Task) => (reverse ? -1 : 1) * a.name.localeCompare(b.name)); return tasks
.slice()
.sort(
(a: Task, b: Task) =>
(reverse ? -1 : 1) * a.name.localeCompare(b.name),
);
default: default:
return tasks.slice(); return tasks.slice();
} }
+14 -10
View File
@@ -1,29 +1,33 @@
import { ref } from 'vue'; import { ref } from "vue";
import { defineStore } from 'pinia'; import { defineStore } from "pinia";
import type { Alert } from '@/types'; import type { Alert } from "@/types";
export const useAlertStore = defineStore('alerts', () => { export const useAlertStore = defineStore("alerts", () => {
const alerts = ref<Alert[]>([]); const alerts = ref<Alert[]>([]);
function addAlert(message: string, type: 'info' | 'success' | 'warning' | 'error', seconds: number) { function addAlert(
message: string,
type: "info" | "success" | "warning" | "error",
seconds: number,
) {
alerts.value.push({ message, type, created: new Date(), seconds }); alerts.value.push({ message, type, created: new Date(), seconds });
} }
function alertSuccess(message: string, seconds = 2) { function alertSuccess(message: string, seconds = 2) {
addAlert(message, 'success', seconds); addAlert(message, "success", seconds);
} }
function alertInfo(message: string, seconds = 3) { function alertInfo(message: string, seconds = 3) {
addAlert(message, 'info', seconds); addAlert(message, "info", seconds);
} }
function alertWarning(message: string, seconds = 4) { function alertWarning(message: string, seconds = 4) {
addAlert(message, 'warning', seconds); addAlert(message, "warning", seconds);
} }
function alertError(message: string, seconds = 5) { function alertError(message: string, seconds = 5) {
addAlert(message, 'error', seconds); addAlert(message, "error", seconds);
} }
return { alerts, alertSuccess, alertInfo, alertWarning, alertError }; return { alerts, alertSuccess, alertInfo, alertWarning, alertError };
}) });
+4 -4
View File
@@ -41,8 +41,8 @@ export interface TaskUpdateData {
} }
export interface Alert { export interface Alert {
message: string message: string;
type: 'info' | 'success' | 'warning' | 'error' type: "info" | "success" | "warning" | "error";
created: Date created: Date;
seconds: number seconds: number;
} }