feat: add shift management functionality

This commit is contained in:
2026-08-26 18:53:15 +03:30
parent 2d96978c38
commit aeef95339d
34 changed files with 1953 additions and 15 deletions
@@ -0,0 +1,101 @@
<script setup>
import Master from "../../layouts/master.vue";
import {onMounted , onUnmounted} from 'vue';
import {useShiftStore} from "../../store/Shift.js";
import {Link} from "@inertiajs/inertia-vue3";
import Card from "../../components/ui/Card.vue";
const shiftStore = useShiftStore();
const props = defineProps({
is_edit: {
type: Boolean,
default: () => false
},
shift: {
type: Object,
default: () => {}
},
});
onMounted(() => {
shiftStore.fetchDoctorOptions();
if (props.is_edit) {
shiftStore.setEditData(props.shift);
}
});
onUnmounted(() => {
shiftStore.resetCreateForm();
shiftStore.is_edit_form = false;
shiftStore.errorsCreateForm = [];
})
</script>
<template>
<master>
<Card class="mb-4">
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] justify-between items-center gap-4">
<h2 class="font-bold text-client-Fontcolor">{{ shiftStore.pageTitle }}</h2>
<Button severity="secondary" asChild v-slot="slotProps">
<Link :class="slotProps.class" :href="shiftStore.endpoints.index">
<span class="text-sm">{{ shiftStore.indexPageTitle }}</span>
<i class="fi fi-rr-list flex text-sm"></i>
</Link>
</Button>
</div>
</Card>
<section class="grid w-full grid-cols-1 lg:grid-cols-[1fr_22rem] gap-4">
<Card>
<div class="rounded-2xl bg-white">
<Form ref="formRef" v-slot="$form" class=" p-4 grid grid-cols-2 gap-4 !w-full">
<FormField v-slot="$field" name="user_id" initialValue="" class="flex flex-col gap-1">
<label>پزشک</label>
<Select class="!text-xs !h-full" placeholder="انتخاب پزشک (خالی = شیفت آزاد)" v-model="shiftStore.createForm.user_id" :options="shiftStore.doctorOptions" optionValue="value" optionLabel="label" showClear :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
<Message v-if="shiftStore.errorsCreateForm?.user_id" severity="error" size="small" variant="simple">{{ shiftStore.errorsCreateForm?.user_id[0] }}</Message>
</FormField>
<FormField v-slot="$field" name="schedule_type" initialValue="" class="flex flex-col gap-1">
<label>نوع زمان‌بندی</label>
<Select class="!text-xs !h-full" placeholder="انتخاب نوع زمان‌بندی" v-model="shiftStore.createForm.schedule_type" :options="shiftStore.scheduleTypeOptions" optionValue="value" optionLabel="label" :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
</FormField>
<FormField v-if="shiftStore.createForm.schedule_type === 'weekly'" v-slot="$field" name="day_of_week" initialValue="" class="flex flex-col gap-1">
<label>روز هفته</label>
<Select class="!text-xs !h-full" placeholder="انتخاب روز هفته" v-model="shiftStore.createForm.day_of_week" :options="shiftStore.dayOptions" optionValue="value" optionLabel="label" :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
<Message v-if="shiftStore.errorsCreateForm?.day_of_week" severity="error" size="small" variant="simple">{{ shiftStore.errorsCreateForm?.day_of_week[0] }}</Message>
</FormField>
<FormField v-else v-slot="$field" name="date" initialValue="" class="flex flex-col gap-1">
<label>تاریخ</label>
<CustomDatePicker v-model="shiftStore.createForm.date" class="!text-xs mt-2" type="date" format="YYYY-MM-DD" placeholder="تاریخ شیفت را انتخاب کنید"></CustomDatePicker>
<Message v-if="shiftStore.errorsCreateForm?.date" severity="error" size="small" variant="simple">{{ shiftStore.errorsCreateForm?.date[0] }}</Message>
</FormField>
<FormField v-slot="$field" name="start_time" initialValue="" class="flex flex-col gap-1">
<label>ساعت شروع</label>
<CustomDatePicker v-model="shiftStore.createForm.start_time" class="!text-xs mt-2" type="time" format="HH:mm" display-format="HH:mm" placeholder="ساعت شروع را انتخاب کنید"></CustomDatePicker>
<Message v-if="shiftStore.errorsCreateForm?.start_time" severity="error" size="small" variant="simple">{{ shiftStore.errorsCreateForm?.start_time[0] }}</Message>
</FormField>
<FormField v-slot="$field" name="end_time" initialValue="" class="flex flex-col gap-1">
<label>ساعت پایان</label>
<CustomDatePicker v-model="shiftStore.createForm.end_time" class="!text-xs mt-2" type="time" format="HH:mm" display-format="HH:mm" placeholder="ساعت پایان را انتخاب کنید"></CustomDatePicker>
<Message v-if="shiftStore.errorsCreateForm?.end_time" severity="error" size="small" variant="simple">{{ shiftStore.errorsCreateForm?.end_time[0] }}</Message>
</FormField>
<FormField v-slot="$field" name="is_active" initialValue="" class="flex flex-col gap-1">
<label>وضعیت</label>
<Select class="!text-xs !h-full" placeholder="انتخاب وضعیت" v-model="shiftStore.createForm.is_active" :options="shiftStore.statusOptions" optionValue="value" optionLabel="label" :pt="{ option : '!text-sm' , placeholder : '!text-sm' , label : '!pt-3' }" />
<Message v-if="shiftStore.errorsCreateForm?.is_active" severity="error" size="small" variant="simple">{{ shiftStore.errorsCreateForm?.is_active[0] }}</Message>
</FormField>
</Form>
</div>
</Card>
<div>
<Card>
<div class="p-4 grid grid-cols-1 gap-4 rounded-2xl">
<div class="grid grid-cols-1 justify-start">
<Button :loading="shiftStore.loaderCreateForm" @click="shiftStore.update_and_store" type="button" severity="secondary" :label="shiftStore.buttonLabel" />
</div>
</div>
</Card>
</div>
</section>
</master>
</template>
+296
View File
@@ -0,0 +1,296 @@
<script setup>
import Master from "../../layouts/master.vue";
import { ref, reactive, watch } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import TableGeneric from "../../components/TableGeneric.vue";
import ShiftCalendar from "../../components/shifts/ShiftCalendar.vue";
import { Link } from "@inertiajs/inertia-vue3";
import { useConfirm } from 'primevue/useconfirm';
import { useShiftStore } from "../../store/Shift.js";
import Button from 'primevue/button';
import Tag from 'primevue/tag';
const shiftStore = useShiftStore();
const props = defineProps({
shifts: {
type: Object,
},
search: {
type: String
},
day_of_week: {
type: [String, Array],
default: null
},
status: {
type: String
},
occupancy: {
type: String
},
permissions_edit: {
type: Boolean
},
permissions_delete: {
type: Boolean
}
})
const shifts = ref(props.shifts ?? []);
const selectedItems = ref([]);
const searchLoader = ref(false);
const filterLoader = ref(false);
const columns = ref(shiftStore.columns)
const queryParams = reactive({
search: props.search ?? '',
day_of_week: props.day_of_week ?? '',
status: props.status ?? '',
occupancy: props.occupancy ?? '',
})
const filters = ref({
status: (props.status && typeof props.status === 'string') ? props.status : null,
occupancy: (props.occupancy && typeof props.occupancy === 'string') ? props.occupancy : null,
day_of_week: shiftStore.parseStringToArray(props.day_of_week),
})
const confirm = useConfirm();
const showCalendar = ref(false);
watch(showCalendar, (visible) => {
if (visible && !shiftStore.calendarShifts.length) {
shiftStore.fetchCalendarData();
}
});
const onCalendarSelect = (shift) => {
if (!props.permissions_edit) {
Inertia.get(shiftStore.endpoints.edit(shift.id));
}
};
const onPageChange = (event) => {
shiftStore.navigateToPage(event.page + 1);
};
const searchData = () => {
searchLoader.value = true;
const searchParams = {
...(queryParams.search && { search: queryParams.search }),
...(queryParams.status && { status: queryParams.status }),
...(queryParams.occupancy && { occupancy: queryParams.occupancy }),
...shiftStore.convertFiltersToArrayParams(filters.value)
};
shiftStore.search(searchParams);
}
const onSelectionChange = (selection) => {
selectedItems.value = selection;
};
const onSearchChange = (value) => {
queryParams.search = value;
searchData();
};
const onFilterChange = (newFilters) => {
filters.value = { ...newFilters };
Object.keys(newFilters).forEach(key => {
if (Array.isArray(newFilters[key])) {
queryParams[key] = newFilters[key].join(',');
} else {
queryParams[key] = newFilters[key] || '';
}
});
};
const applyFilters = () => {
filterLoader.value = true;
searchData();
};
const clearFilters = () => {
filters.value = {
status: null,
occupancy: null,
day_of_week: [],
};
queryParams.status = '';
queryParams.occupancy = '';
queryParams.day_of_week = '';
searchData();
};
const showDeleteConfirm = (data) => {
confirm.require({
group: 'deleteTemplate',
header: 'مطمعن هستید؟',
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
icon: 'fi fi-rr-info',
rejectProps: {
label: 'انصراف',
icon: 'fi fi-rr-ban',
outlined: true,
size: 'small',
class: '!text-xs !flex pt-1 !item-center',
iconClass: '!flex !text-xs',
},
acceptProps: {
label: 'حذف',
icon: 'fi fi-rr-check',
size: 'small',
class: '!text-xs !flex pt-1 !item-center',
iconClass: '!flex !text-xs',
},
accept: () => {
shiftStore.deleteRecord(data.id);
},
reject: () => {
}
});
}
const confirmDeleteSelected = (ids) => {
confirm.require({
group: 'deleteTemplate',
header: 'مطمعن هستید؟',
message: 'اگر به عملیات اطمینان دارید، تأیید کنید.',
icon: 'fi fi-rr-info',
rejectProps: {
label: 'انصراف',
icon: 'fi fi-rr-ban',
outlined: true,
size: 'small',
class: '!text-xs !flex pt-1 !item-center',
iconClass: '!flex !text-xs',
},
acceptProps: {
label: 'حذف',
icon: 'fi fi-rr-check',
size: 'small',
class: '!text-xs !flex pt-1 !item-center',
iconClass: '!flex !text-xs',
},
accept: () => {
shiftStore.bulkDelete(ids);
},
reject: () => {
}
});
}
</script>
<template>
<master>
<div class="mb-4 grid grid-cols-2a justify-between items-center bg-white border-[1px] border-client-gray/20 py-4 px-4 rounded-2xl">
<h2 class="font-bold text-client-Fontcolor">{{ shiftStore.indexPageTitle }}</h2>
<div class="grid grid-cols-2a gap-2">
<Button
severity="secondary"
outlined
:pt="{icon : 'flex'}"
:icon="showCalendar ? 'fi fi-rr-list' : 'fi fi-rr-calendar'"
:label="showCalendar ? 'نمای جدول' : 'نمای تقویم'"
class="!text-sm"
@click="showCalendar = !showCalendar"
/>
<Button severity="secondary" asChild v-slot="slotProps">
<Link :class="slotProps.class" :href="shiftStore.endpoints.create">
<span class="text-sm">{{ shiftStore.pageTitle }}</span>
<i class="fi fi-rr-plus flex text-sm"></i>
</Link>
</Button>
</div>
</div>
<div v-if="showCalendar" class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
<ShiftCalendar
:shifts="shiftStore.calendarShifts"
@select-shift="onCalendarSelect"
/>
</div>
<div v-else class="card border-[1px] border-client-gray/20 bg-white rounded-2xl">
<TableGeneric
:data="shifts.data"
:columns="columns"
:selectable="true"
:pagination="shifts.meta"
:searchable="true"
:searchValue="queryParams.search"
:filters="filters"
:filterLoader="filterLoader"
v-model="selectedItems"
@selection-change="onSelectionChange"
@page-change="onPageChange"
@search-change="onSearchChange"
@filter-change="onFilterChange"
@apply-filters="applyFilters"
@clear-filters="clearFilters"
>
<template #bulk-action="{ selection }" v-if="!permissions_delete">
<Button
label="حذف همه"
size="small"
:pt="{icon : 'flex'}"
icon="fi fi-rr-trash"
severity="danger"
class="!text-xs !flex"
outlined
@click="() => confirmDeleteSelected(selection.map(shift => shift.id))"
/>
</template>
<template #column-doctor="{ data }">
<span v-if="data.doctor" class="text-right text-sm">{{ data.doctor.fullname }}</span>
<Tag v-else class="!text-xs" value="آزاد" severity="success" />
</template>
<template #column-occupancy="{ data }">
<Tag
class="!text-xs"
:value="data.user_id ? 'اختصاص یافته' : 'آزاد'"
:severity="shiftStore.getOccupancySeverity(data.user_id)"
/>
</template>
<template #column-status="{ data }">
<Tag
class="!text-xs"
:value="data.is_active ? 'فعال' : 'غیرفعال'"
:severity="shiftStore.getStatusSeverity(data.is_active)"
/>
</template>
<template #column-actions="{ data }">
<div class="grid grid-cols-2a justify-start gap-2">
<Button
v-if="!permissions_delete"
type="button"
class="!w-8 !h-8"
variant="outlined"
severity="danger"
icon="fi fi-rr-trash"
:pt="{icon : '!mt-2 !text-xs'}"
@click="showDeleteConfirm(data)"
/>
<Button
v-if="!permissions_edit"
severity="info"
variant="outlined"
asChild
v-slot="slotProps"
>
<Link :class="[slotProps.class , '!w-8 !h-8']" :href="shiftStore.endpoints.edit(data.id)">
<i class="fi fi-rr-pen-clip flex !text-xs"></i>
</Link>
</Button>
</div>
</template>
</TableGeneric>
</div>
</master>
</template>
@@ -86,6 +86,18 @@ const items: Ref<SidebarItem[]> = ref([
permission: "reports.index",
expanded: false,
},
{
label: "مدیریت شیفت ها",
icon: "fi fi-rr-calendar",
to: "",
key: 'shifts',
permission: null,
expanded: false,
items: [
{ label: "همه شیفت ها", icon: "fi fi-rr-wifi-1", to: "/admin/shifts" , permission: "shifts.index" },
{ label: "افزودن شیفت جدید", icon: "fi fi-rr-wifi-1", to: "/admin/shifts/create" , permission: "shifts.create" },
],
},
{
label: "مدیریت رسانه",
icon: "fi fi-rr-gallery",
@@ -0,0 +1,103 @@
<script setup>
import { computed, ref } from 'vue';
import FullCalendar from '@fullcalendar/vue3';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import faLocale from '@fullcalendar/core/locales/fa';
import Button from 'primevue/button';
import { buildShiftEvent, formatJalaliDay, formatJalaliRange } from '../../../Shared/shiftCalendarEvents';
const props = defineProps({
shifts: {
type: Array,
required: true
}
})
const emit = defineEmits(['select-shift']);
const doctorPalette = ['#576989', '#800E13', '#50CD89', '#7239EA', '#FFC700', '#F1416C', '#009EF7', '#965457'];
const calendarRef = ref(null);
const viewTitle = ref('');
const doctors = computed(() => {
const unique = new Map();
props.shifts.forEach((shift) => {
if (shift.doctor) unique.set(shift.doctor.id, shift.doctor);
});
return [...unique.values()];
});
const doctorColor = (doctorId) => {
const index = doctors.value.findIndex((doctor) => doctor.id === doctorId);
return doctorPalette[index % doctorPalette.length];
};
const events = computed(() => props.shifts.map((shift) => {
const overrides = shift.doctor
? {
title: shift.doctor.fullname,
classNames: ['shift-event--assigned', ...(shift.is_active ? [] : ['shift-event--inactive'])],
backgroundColor: doctorColor(shift.doctor.id),
borderColor: doctorColor(shift.doctor.id),
}
: {
title: 'شیفت آزاد',
classNames: ['shift-event--open', ...(shift.is_active ? [] : ['shift-event--inactive'])],
};
return buildShiftEvent(shift, overrides);
}));
const calendarOptions = computed(() => ({
plugins: [timeGridPlugin, interactionPlugin],
initialView: 'timeGridWeek',
locale: faLocale,
direction: 'rtl',
firstDay: 6,
headerToolbar: false,
allDaySlot: false,
height: 'auto',
nowIndicator: true,
slotLabelFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
eventTimeFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
dayHeaderContent: (args) => ({
html: `${args.text}<span class="shift-calendar-day-date">${formatJalaliDay(args.date)}</span>`
}),
datesSet: (info) => {
viewTitle.value = formatJalaliRange(info.start, info.end);
},
events: events.value,
eventClick: (info) => {
emit('select-shift', info.event.extendedProps.shift);
},
}))
const goPrev = () => calendarRef.value?.getApi().prev();
const goNext = () => calendarRef.value?.getApi().next();
const goToday = () => calendarRef.value?.getApi().today();
</script>
<template>
<div class="shift-calendar p-4">
<div class="flex flex-wrap items-center justify-between gap-3 pb-4">
<div class="flex items-center gap-2">
<Button label="امروز" size="small" outlined severity="secondary" class="!text-xs" @click="goToday" />
<Button icon="fi fi-rr-angle-small-right" size="small" outlined severity="secondary" class="!w-8 !h-8" :pt="{icon : 'flex !text-xs'}" @click="goPrev" />
<Button icon="fi fi-rr-angle-small-left" size="small" outlined severity="secondary" class="!w-8 !h-8" :pt="{icon : 'flex !text-xs'}" @click="goNext" />
<span class="text-sm font-bold text-client-Fontcolor">{{ viewTitle }}</span>
</div>
<ul class="flex flex-wrap items-center gap-4">
<li v-for="doctor in doctors" :key="doctor.id" class="flex items-center gap-1.5">
<span class="w-3 h-3 rounded" :style="{ background: doctorColor(doctor.id) }"></span>
<span class="text-xs text-client-Fontcolor">{{ doctor.fullname }}</span>
</li>
<li class="flex items-center gap-1.5">
<span class="w-3 h-3 rounded border border-dashed border-[#7188AD] bg-[#F6F7F9]"></span>
<span class="text-xs text-client-Fontcolor">شیفت آزاد</span>
</li>
</ul>
</div>
<FullCalendar ref="calendarRef" :options="calendarOptions" />
</div>
</template>
+291
View File
@@ -0,0 +1,291 @@
import { defineStore } from 'pinia'
import axios from "axios";
import { Inertia } from '@inertiajs/inertia'
export const useShiftStore = defineStore('ShiftStore', {
state: () => ({
createForm: {
user_id: '',
schedule_type: 'weekly',
day_of_week: '',
date: '',
start_time: '',
end_time: '',
is_active: true,
},
dayOptions: [
{ label: 'شنبه', value: 'شنبه' },
{ label: 'یکشنبه', value: 'یکشنبه' },
{ label: 'دوشنبه', value: 'دوشنبه' },
{ label: 'سه‌شنبه', value: 'سه‌شنبه' },
{ label: 'چهارشنبه', value: 'چهارشنبه' },
{ label: 'پنج‌شنبه', value: 'پنج‌شنبه' },
{ label: 'جمعه', value: 'جمعه' },
],
scheduleTypeOptions: [
{ label: 'هفتگی', value: 'weekly' },
{ label: 'تاریخ مشخص', value: 'date' },
],
statusOptions: [
{ label: 'فعال', value: true },
{ label: 'غیرفعال', value: false },
],
doctorOptions: [],
calendarShifts: [],
loaderCalendar: false,
loaderCreateForm: false,
is_edit_form: false,
errorsCreateForm: [],
is_edit_id: null,
filters: {},
columns: [
{
key: 'doctor',
title: 'پزشک',
slot: 'doctor',
},
{
key: 'day_of_week',
title: 'روز / تاریخ',
render: (data) => data.date ?? data.day_of_week ?? 'ثبت نشده',
filter: {
type: 'multiselect',
options: [
{ label: 'شنبه', value: 'شنبه' },
{ label: 'یکشنبه', value: 'یکشنبه' },
{ label: 'دوشنبه', value: 'دوشنبه' },
{ label: 'سه‌شنبه', value: 'سه‌شنبه' },
{ label: 'چهارشنبه', value: 'چهارشنبه' },
{ label: 'پنج‌شنبه', value: 'پنج‌شنبه' },
{ label: 'جمعه', value: 'جمعه' },
],
placeholder: 'انتخاب روز...'
}
},
{
key: 'start_time',
title: 'ساعت شروع',
},
{
key: 'end_time',
title: 'ساعت پایان',
},
{
key: 'occupancy',
title: 'وضعیت اختصاص',
slot: 'occupancy',
filter: {
type: 'dropdown',
options: [
{ label: 'آزاد', value: 'آزاد' },
{ label: 'اختصاص یافته', value: 'اختصاص یافته' },
],
placeholder: 'انتخاب وضعیت اختصاص...'
}
},
{
key: 'status',
title: 'وضعیت',
slot: 'status',
filter: {
type: 'dropdown',
options: [
{ label: 'فعال', value: 'فعال' },
{ label: 'غیرفعال', value: 'غیرفعال' },
],
placeholder: 'انتخاب وضعیت...'
}
},
{
key: 'created_at',
title: 'تاریخ ثبت',
},
{
key: 'actions',
title: 'عملیات',
hasSort: false,
slot: true,
},
],
endpoints: {
index: '/admin/shifts',
create: '/admin/shifts/create',
store: '/admin/shifts',
edit: (id) => `/admin/shifts/${id}/edit`,
update: (id) => `/admin/shifts/${id}`,
delete: (id) => `/admin/shifts/${id}`,
bulkDelete: '/admin/shifts-bulk-delete',
calendarData: '/admin/shifts-calendar-data',
doctorsOptions: '/admin/doctors-options',
}
}),
getters: {
buttonLabel: (state) => {
return state.is_edit_id ? 'ویرایش شیفت' : 'ثبت و ایجاد شیفت جدید';
},
pageTitle: (state) => {
return state.is_edit_id ? 'ویرایش شیفت' : 'افزودن شیفت جدید';
},
indexPageTitle: (state) => {
return 'نمایش همه شیفت ها';
}
},
actions: {
update_and_store() {
const url = this.is_edit_id ?
this.endpoints.update(this.is_edit_id) :
this.endpoints.store;
const method = this.is_edit_id ? 'patch' : 'post';
this.loaderCreateForm = true;
axios({
method: method,
url: url,
data: {
user_id: this.createForm.user_id || null,
day_of_week: this.createForm.schedule_type === 'weekly' ? this.createForm.day_of_week : null,
date: this.createForm.schedule_type === 'date' ? this.formatDate(this.createForm.date) : null,
start_time: this.formatTime(this.createForm.start_time),
end_time: this.formatTime(this.createForm.end_time),
is_active: this.createForm.is_active,
}
}).then(res => {
this.errorsCreateForm = []
this.loaderCreateForm = false;
if (this.is_edit_id) {
Inertia.get(this.endpoints.edit(this.is_edit_id));
} else {
this.resetCreateForm();
Inertia.get(this.endpoints.create);
}
}).catch(error => {
this.loaderCreateForm = false;
this.errorsCreateForm = error.response.data?.errors ?? [];
});
},
resetCreateForm() {
this.createForm = {
user_id: '',
schedule_type: 'weekly',
day_of_week: '',
date: '',
start_time: '',
end_time: '',
is_active: true,
}
this.is_edit_id = null;
this.errorsCreateForm = [];
},
setEditData(data) {
this.is_edit_id = data.id;
this.createForm = {
user_id: data.user_id ?? '',
schedule_type: data.date_raw ? 'date' : 'weekly',
day_of_week: data.day_of_week ?? '',
date: data.date_raw ?? '',
start_time: data.start_time ?? '',
end_time: data.end_time ?? '',
is_active: data.is_active,
};
},
formatDate(value) {
if (!value) return null;
return String(value).split(' ')[0];
},
formatTime(value) {
if (!value) return null;
const parts = String(value).split(' ');
const time = parts.length > 1 ? parts[1] : parts[0];
return time.substring(0, 5);
},
fetchCalendarData() {
this.loaderCalendar = true;
axios.get(this.endpoints.calendarData).then(res => {
this.calendarShifts = res.data?.data ?? [];
}).finally(() => {
this.loaderCalendar = false;
});
},
fetchDoctorOptions() {
axios.get(this.endpoints.doctorsOptions).then(res => {
this.doctorOptions = res.data?.data ?? [];
});
},
deleteRecord(id) {
return new Promise((resolve, reject) => {
axios.delete(this.endpoints.delete(id))
.then(response => {
Inertia.get(this.endpoints.index);
resolve(response);
})
.catch(error => {
reject(error);
});
});
},
bulkDelete(ids) {
return new Promise((resolve, reject) => {
axios.post(this.endpoints.bulkDelete, { ids })
.then(response => {
Inertia.get(this.endpoints.index);
resolve(response);
})
.catch(error => {
reject(error);
});
});
},
search(params) {
return Inertia.get(this.endpoints.index, params, {
preserveState: false
});
},
navigateToPage(page) {
return Inertia.get(this.endpoints.index, { page }, { preserveState: true });
},
getStatusSeverity(isActive) {
return isActive ? 'info' : 'secondary';
},
getOccupancySeverity(userId) {
return userId ? 'info' : 'success';
},
convertFiltersToArrayParams(filters) {
const params = {};
Object.keys(filters).forEach(key => {
if (Array.isArray(filters[key]) && filters[key].length > 0) {
filters[key].forEach((value, index) => {
params[`${key}[${index}]`] = value;
});
} else if (!Array.isArray(filters[key]) && filters[key]) {
params[key] = filters[key];
}
});
return params;
},
parseStringToArray(value) {
if (Array.isArray(value)) {
return value.filter(item => item !== '' && item != null);
}
if (value && typeof value === 'string' && value.trim() !== '') {
return value.split(',').map(item => item.trim()).filter(item => item !== '');
}
return [];
},
}
})