Files
monica/resources/js/Shared/Modules/Reminders.vue

527 lines
19 KiB
Vue

<script setup>
import { nextTick, ref, useTemplateRef } from 'vue';
import { useForm } from '@inertiajs/vue3';
import { flash } from '@/methods';
import { trans } from 'laravel-vue-i18n';
import { DatePicker } from 'v-calendar';
import 'v-calendar/style.css';
import { Tooltip as ATooltip } from 'ant-design-vue';
import PrettyButton from '@/Shared/Form/PrettyButton.vue';
import PrettySpan from '@/Shared/Form/PrettySpan.vue';
import TextInput from '@/Shared/Form/TextInput.vue';
import Dropdown from '@/Shared/Form/Dropdown.vue';
import Errors from '@/Shared/Form/Errors.vue';
import { Bell } from 'lucide-vue-next';
const props = defineProps({
data: Object,
});
const label = useTemplateRef('label');
const labels = useTemplateRef('labels');
const loadingState = ref('');
const addReminderModalShown = ref(false);
const localReminders = ref(props.data.reminders);
const editedReminderId = ref(0);
const masks = ref({
modelValue: 'YYYY-MM-DD',
});
const form = useForm({
label: '',
reminderChoice: '',
day: '',
month: '',
choice: '',
date: '',
frequencyType: '',
frequencyNumber: 0,
errors: [],
});
const showCreateReminderModal = () => {
form.errors = [];
form.label = '';
form.choice = 'full_date';
form.day = '';
form.month = '';
form.reminderChoice = 'recurring';
form.date = '';
form.frequencyType = 'recurring_year';
form.frequencyNumber = 1;
addReminderModalShown.value = true;
nextTick().then(() => label.value.focus());
};
const showEditReminderModal = (reminder) => {
form.errors = [];
editedReminderId.value = reminder.id;
form.label = reminder.label;
form.day = reminder.day;
form.month = reminder.month;
form.date = reminder.date;
form.reminderChoice = reminder.reminder_choice;
form.frequencyNumber = reminder.frequency_number;
form.frequencyType = reminder.type;
form.choice = reminder.choice;
nextTick().then(() => labels.value[0].focus());
};
const submit = () => {
loadingState.value = 'loading';
axios
.post(props.data.url.store, form)
.then((response) => {
flash(trans('The reminder has been created'), 'success');
localReminders.value.unshift(response.data.data);
loadingState.value = '';
addReminderModalShown.value = false;
})
.catch((error) => {
loadingState.value = '';
form.errors = error.response.data;
});
};
const update = (reminder) => {
loadingState.value = 'loading';
axios
.put(reminder.url.update, form)
.then((response) => {
loadingState.value = '';
flash(trans('The reminder has been updated'), 'success');
localReminders.value[localReminders.value.findIndex((x) => x.id === reminder.id)] = response.data.data;
editedReminderId.value = 0;
})
.catch((error) => {
loadingState.value = '';
form.errors = error.response.data;
});
};
const destroy = (reminder) => {
if (confirm(trans('Are you sure? This action cannot be undone.'))) {
axios
.delete(reminder.url.destroy)
.then(() => {
flash(trans('The reminder has been deleted'), 'success');
let id = localReminders.value.findIndex((x) => x.id === reminder.id);
localReminders.value.splice(id, 1);
})
.catch((error) => {
loadingState.value = null;
form.errors = error.response.data;
});
}
};
</script>
<template>
<div class="mb-10">
<!-- title + cta -->
<div class="mb-3 items-center justify-between border-b border-gray-200 pb-2 dark:border-gray-700 sm:flex">
<div class="mb-2 sm:mb-0 flex items-center gap-2">
<Bell class="h-4 w-4 text-gray-600" />
<span class="font-semibold"> {{ $t('Reminders') }} </span>
</div>
<pretty-button
:text="$t('Add a reminder')"
:icon="'plus'"
:class="'w-full sm:w-fit'"
@click="showCreateReminderModal" />
</div>
<!-- add a reminder modal -->
<form
v-if="addReminderModalShown"
class="mb-6 rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"
@submit.prevent="submit()">
<div class="border-b border-gray-200 dark:border-gray-700">
<div v-if="form.errors.length > 0" class="p-5">
<errors :errors="form.errors" />
</div>
<!-- name -->
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<text-input
ref="label"
v-model="form.label"
:label="$t('Name of the reminder')"
:type="'text'"
:autofocus="true"
:input-class="'block w-full'"
:required="true"
:autocomplete="false"
:maxlength="255"
@esc-key-pressed="addReminderModalShown = false" />
</div>
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<!-- case: I know the exact date -->
<div class="mb-2 flex items-center">
<input
id="full_date"
v-model="form.choice"
value="full_date"
name="date"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="full_date"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('I know the exact date, including the year') }}
</label>
</div>
<div v-if="form.choice === 'full_date'" class="mb-4 ms-6">
<DatePicker
v-model.string="form.date"
class="inline-block h-full"
:masks="masks"
:locale="$page.props.auth.user?.locale_ietf"
:is-dark="isDark()">
<template #default="{ inputValue, inputEvents }">
<input
class="rounded-xs border bg-white px-2 py-1 dark:bg-gray-900"
:value="inputValue"
v-on="inputEvents" />
</template>
</DatePicker>
</div>
<!-- case: date and month -->
<div class="flex items-center">
<input
id="month_day"
v-model="form.choice"
value="month_day"
name="date"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="month_day"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('I only know the day and month, not the year') }}
</label>
</div>
<div v-if="form.choice === 'month_day'" class="ms-6 mt-2 flex">
<Dropdown
v-model="form.month"
:data="data.months"
:required="true"
:class="'mb-5 me-2'"
:placeholder="$t('Choose a value')"
:dropdown-class="'block w-full'"
:label="$t('Month')" />
<Dropdown
v-model="form.day"
:data="data.days"
:required="true"
:class="'mb-5'"
:placeholder="$t('Choose a value')"
:dropdown-class="'block w-full'"
:label="$t('Day')" />
</div>
</div>
<!-- reminder options -->
<div class="p-5">
<p class="mb-1">{{ $t('How often should we remind you about this date?') }}</p>
<p class="mb-1 text-sm text-gray-600 dark:text-gray-400">
{{ $t('If the date is in the past, the next occurence of the date will be next year.') }}
</p>
<div class="ms-4 mt-4">
<div class="mb-2 flex items-center">
<input
id="one_time"
v-model="form.reminderChoice"
value="one_time"
name="reminder-frequency"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="one_time"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('Only once, when the next occurence of the date occurs.') }}
</label>
</div>
<div class="mb-2 flex items-center">
<input
id="recurring"
v-model="form.reminderChoice"
value="recurring"
name="reminder-frequency"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="recurring"
class="ms-3 block flex cursor-pointer items-center text-sm font-medium text-gray-700 dark:text-gray-300">
<span class="me-2">{{ $t('Every') }}</span>
<Dropdown
:id="'frequency-number'"
v-model="form.frequencyNumber"
dropdown-class="me-2"
:required="required"
:data="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" />
<Dropdown
:id="'frequency-type'"
v-model="form.frequencyType"
dropdown-class="me-2"
:required="required"
:data="[
{ id: 'recurring_day', name: $t('day') },
{ id: 'recurring_month', name: $t('month') },
{ id: 'recurring_year', name: $t('year') },
]" />
<span>{{ $t('after the next occurence of the date.') }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="flex justify-between p-5">
<pretty-span :text="$t('Cancel')" :class="'me-3'" @click="addReminderModalShown = false" />
<pretty-button :text="$t('Add date')" :state="loadingState" :icon="'plus'" :class="'save'" />
</div>
</form>
<!-- reminders -->
<div v-if="localReminders.length > 0">
<ul class="mb-4 rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900">
<li
v-for="reminder in localReminders"
:key="reminder.id"
class="item-list border-b border-gray-200 hover:bg-slate-50 dark:border-gray-700 dark:bg-slate-900 dark:hover:bg-slate-800">
<!-- reminder -->
<div class="flex items-center justify-between px-3 py-2">
<div class="flex items-center">
<span class="me-2 text-sm text-gray-500">{{ reminder.date }}</span>
<span class="me-2">{{ reminder.label }}</span>
<!-- recurring icon -->
<a-tooltip
v-if="reminder.type !== 'one_time'"
placement="topLeft"
title="Recurring"
arrow-point-at-center>
<RefreshIcon />
</a-tooltip>
</div>
<!-- actions -->
<ul class="text-sm">
<li
class="me-4 inline cursor-pointer text-blue-500 hover:underline"
@click="showEditReminderModal(reminder)">
{{ $t('Edit') }}
</li>
<li class="inline cursor-pointer text-red-500 hover:text-red-900" @click="destroy(reminder)">
{{ $t('Delete') }}
</li>
</ul>
</div>
<!-- edit reminder modal -->
<form
v-if="editedReminderId === reminder.id"
class="bg-gray-50 dark:bg-gray-900"
@submit.prevent="update(reminder)">
<div class="border-b border-gray-200 dark:border-gray-700">
<div v-if="form.errors.length > 0" class="p-5">
<errors :errors="form.errors" />
</div>
<!-- name -->
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<text-input
ref="labels"
v-model="form.label"
:label="$t('Name of the reminder')"
:type="'text'"
:autofocus="true"
:input-class="'block w-full'"
:required="true"
:autocomplete="false"
:maxlength="255"
@esc-key-pressed="addReminderModalShown = false" />
</div>
<div class="border-b border-gray-200 p-5 dark:border-gray-700">
<!-- case: I know the exact date -->
<div class="mb-2 flex items-center">
<input
id="full_date"
v-model="form.choice"
value="full_date"
name="date"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="full_date"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('I know the exact date, including the year') }}
</label>
</div>
<div v-if="form.choice === 'full_date'" class="mb-4 ms-6">
<DatePicker
v-model.string="form.date"
class="inline-block h-full"
:masks="masks"
:locale="$page.props.auth.user?.locale_ietf"
:is-dark="isDark()">
<template #default="{ inputValue, inputEvents }">
<input
class="rounded-xs border bg-white px-2 py-1 dark:bg-gray-900"
:value="inputValue"
v-on="inputEvents" />
</template>
</DatePicker>
</div>
<!-- case: date and month -->
<div class="flex items-center">
<input
id="month_day"
v-model="form.choice"
value="month_day"
name="date"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="month_day"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('I only know the day and month, not the year') }}
</label>
</div>
<div v-if="form.choice === 'month_day'" class="ms-6 mt-2 flex">
<Dropdown
v-model="form.month"
:data="data.months"
:required="true"
:class="'me-2'"
:placeholder="$t('Choose a value')"
:dropdown-class="'block w-full'"
:label="$t('Month')" />
<Dropdown
v-model="form.day"
:data="data.days"
:required="true"
:placeholder="$t('Choose a value')"
:dropdown-class="'block w-full'"
:label="$t('Day')" />
</div>
</div>
<!-- reminder options -->
<div class="p-5">
<p class="mb-1">{{ $t('How often should we remind you about this date?') }}</p>
<p class="mb-1 text-sm text-gray-600 dark:text-gray-400">
{{ $t('If the date is in the past, the next occurence of the date will be next year.') }}
</p>
<div class="ms-4 mt-4">
<div class="mb-2 flex items-center">
<input
id="one_time"
v-model="form.reminderChoice"
value="one_time"
name="reminder-frequency"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="one_time"
class="ms-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
{{ $t('Only once, when the next occurence of the date occurs.') }}
</label>
</div>
<div class="mb-2 flex items-center">
<input
id="recurring"
v-model="form.reminderChoice"
value="recurring"
name="reminder-frequency"
type="radio"
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
<label
for="recurring"
class="ms-3 block flex cursor-pointer items-center text-sm font-medium text-gray-700 dark:text-gray-300">
<span class="me-2">{{ $t('Every') }}</span>
<Dropdown
:id="'frequency-number'"
v-model="form.frequencyNumber"
dropdown-class="me-2"
:required="required"
:data="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" />
<Dropdown
:id="'frequency-type'"
v-model="form.frequencyType"
dropdown-class="me-2"
:required="required"
:data="[
{ id: 'recurring_day', name: $t('day') },
{ id: 'recurring_month', name: $t('month') },
{ id: 'recurring_year', name: $t('year') },
]" />
<span>{{ $t('after the next occurence of the date.') }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="flex justify-between p-5">
<pretty-span :text="$t('Cancel')" :class="'me-3'" @click="editedReminderId = 0" />
<pretty-button :text="$t('Save')" :state="loadingState" :icon="'check'" :class="'save'" />
</div>
</form>
</li>
</ul>
</div>
<!-- blank state -->
<div
v-if="localReminders.length === 0"
class="mb-6 rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900">
<img src="/img/dashboard_blank_reminders.svg" :alt="$t('Reminders')" class="mx-auto mt-4 h-14 w-14" />
<p class="px-5 pb-5 pt-2 text-center">{{ $t('There are no reminders yet.') }}</p>
</div>
</div>
</template>
<style lang="scss" scoped>
.icon-sidebar {
color: #737e8d;
top: -2px;
}
.item-list {
&:hover:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
&:last-child {
border-bottom: 0;
}
&:hover:last-child {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
}
</style>