refactor schedule module, added new filters
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Containers\Schedule\UI\WEB\Controllers;
|
namespace App\Containers\Schedule\UI\WEB\Controllers;
|
||||||
|
|
||||||
|
use App\Containers\InstituteStructure\Models\Faculty;
|
||||||
use App\Containers\Schedule\Models\EducationalGroup;
|
use App\Containers\Schedule\Models\EducationalGroup;
|
||||||
use App\Containers\Schedule\UI\WEB\Transformers\EducationalGroupResource;
|
use App\Containers\Schedule\UI\WEB\Transformers\EducationalGroupResource;
|
||||||
use App\Ship\Contracts\SeoServiceInterface;
|
use App\Ship\Contracts\SeoServiceInterface;
|
||||||
@@ -25,7 +26,11 @@ class ClientScheduleController extends Controller
|
|||||||
})
|
})
|
||||||
->when(request()->input('form'), function ($query, $form) {
|
->when(request()->input('form'), function ($query, $form) {
|
||||||
$query->where('education_form_id', FormEducation::fromName($form)->value);
|
$query->where('education_form_id', FormEducation::fromName($form)->value);
|
||||||
|
})
|
||||||
|
->when(request()->input('faculty'), function ($query, $facultySlug) {
|
||||||
|
$query->whereHas('faculty', function ($q) use ($facultySlug) {
|
||||||
|
$q->where('slug', $facultySlug);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
->with('schedules')
|
->with('schedules')
|
||||||
->with('faculty')
|
->with('faculty')
|
||||||
@@ -49,12 +54,19 @@ class ClientScheduleController extends Controller
|
|||||||
$forms_education[$case->name] = $case->getLabel();
|
$forms_education[$case->name] = $case->getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$faculties = Faculty::query()->where('is_active', true)->get();
|
||||||
|
|
||||||
$filters = [
|
$filters = [
|
||||||
'form_education_filter' => [
|
'form_education_filter' => [
|
||||||
'type' => 'form',
|
'type' => 'form',
|
||||||
'value' => request()->input('form'),
|
'value' => request()->input('form'),
|
||||||
'param' => 'form'
|
'param' => 'form'
|
||||||
],
|
],
|
||||||
|
'faculty_filter' => [
|
||||||
|
'type' => 'faculty',
|
||||||
|
'value' => request()->input('faculty'),
|
||||||
|
'param' => 'faculty'
|
||||||
|
],
|
||||||
'search_filter' => [
|
'search_filter' => [
|
||||||
'type' => 'search',
|
'type' => 'search',
|
||||||
'value' => $request->input('search'),
|
'value' => $request->input('search'),
|
||||||
@@ -77,6 +89,7 @@ class ClientScheduleController extends Controller
|
|||||||
'schedulesByFaculty' => inertia()->deepMerge(fn() => $schedulesByFaculty),
|
'schedulesByFaculty' => inertia()->deepMerge(fn() => $schedulesByFaculty),
|
||||||
'schedules_paginator' => $schedulesPaginate,
|
'schedules_paginator' => $schedulesPaginate,
|
||||||
'seo' => $seo,
|
'seo' => $seo,
|
||||||
|
'faculties' => $faculties,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,21 +12,25 @@ import FormEducationalFilter from "@/componentss/shared/filter/filters/FormEduca
|
|||||||
import MetaTags from "@/componentss/shared/SEO/MetaTags.vue";
|
import MetaTags from "@/componentss/shared/SEO/MetaTags.vue";
|
||||||
import BreadcrumbsItem from "@/componentss/shared/Breadcrumbs/BreadcrumbsItem.vue";
|
import BreadcrumbsItem from "@/componentss/shared/Breadcrumbs/BreadcrumbsItem.vue";
|
||||||
import BaseBreadcrumbs from "@/componentss/shared/Breadcrumbs/BaseBreadcrumbs.vue";
|
import BaseBreadcrumbs from "@/componentss/shared/Breadcrumbs/BaseBreadcrumbs.vue";
|
||||||
|
import FavoriteFilter from "@/componentss/features/schedules/components/FavoriteFilter.vue";
|
||||||
|
import FacultyFilter from "@/componentss/features/schedules/components/FacultyFilter.vue";
|
||||||
|
import SearchFilter from "@/componentss/features/schedules/components/SearchFilter.vue";
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Index",
|
name: "Index",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searchInput: this.filters.search_filter.value,
|
|
||||||
favoriteGroups: JSON.parse(localStorage.getItem('favoriteGroups')),
|
favoriteGroups: JSON.parse(localStorage.getItem('favoriteGroups')),
|
||||||
showFavorites: false,
|
showFavorites: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
currentPageLoaded: this.schedules_paginator.current_page,
|
currentPageLoaded: this.schedules_paginator.current_page,
|
||||||
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
SearchFilter,
|
||||||
|
FacultyFilter,
|
||||||
|
FavoriteFilter,
|
||||||
BaseBreadcrumbs, BreadcrumbsItem,
|
BaseBreadcrumbs, BreadcrumbsItem,
|
||||||
WhenVisible,
|
WhenVisible,
|
||||||
MetaTags,
|
MetaTags,
|
||||||
@@ -57,34 +61,12 @@ export default {
|
|||||||
},
|
},
|
||||||
breadcrumbs: {
|
breadcrumbs: {
|
||||||
type: Object
|
type: Object
|
||||||
}
|
},
|
||||||
|
faculties: {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
search: debounce(function () {
|
|
||||||
if(this.searchInput === "") {
|
|
||||||
let url = new URL(window.location.href);
|
|
||||||
url.searchParams.delete('search');
|
|
||||||
let newUrl = url.toString();
|
|
||||||
this.$inertia.visit(newUrl,{
|
|
||||||
method: 'get',
|
|
||||||
preserveState: true,
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let url = new URL(window.location.href);
|
|
||||||
url.searchParams.delete('page');
|
|
||||||
let newUrl = url.toString();
|
|
||||||
this.$inertia.visit(newUrl,{
|
|
||||||
method: 'get',
|
|
||||||
data: {
|
|
||||||
search: this.searchInput,
|
|
||||||
},
|
|
||||||
preserveState: true,
|
|
||||||
replace: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 500),
|
|
||||||
|
|
||||||
toggleFavorite(group) {
|
toggleFavorite(group) {
|
||||||
if (!this.favoriteGroups) {
|
if (!this.favoriteGroups) {
|
||||||
this.favoriteGroups = [];
|
this.favoriteGroups = [];
|
||||||
@@ -107,66 +89,6 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.favoriteGroups.some(g => g === group);
|
return this.favoriteGroups.some(g => g === group);
|
||||||
},
|
},
|
||||||
toggleShowFavorites() {
|
|
||||||
this.loading = true; // Включаем состояние загрузки
|
|
||||||
|
|
||||||
if (this.filters.favorite_filter.value === null) {
|
|
||||||
this.filterFavorite(() => {
|
|
||||||
this.loading = false; // Выключаем состояние загрузки после завершения
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.clearFilterFavorite(() => {
|
|
||||||
this.loading = false; // Выключаем состояние загрузки после завершения
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
filterFavorite: debounce(function (callback) {
|
|
||||||
let url = new URL(window.location.href);
|
|
||||||
// Создаем массив для хранения всех ключей, которые нужно удалить
|
|
||||||
const keysToDelete = [];
|
|
||||||
|
|
||||||
// Перебираем все параметры и добавляем ключи, начинающиеся с 'category', в массив
|
|
||||||
for (const [key] of url.searchParams) {
|
|
||||||
if (key.startsWith('favorite')) {
|
|
||||||
keysToDelete.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Удаляем все ключи из массива
|
|
||||||
keysToDelete.forEach(key => url.searchParams.delete(key));
|
|
||||||
let newUrl = url.toString();
|
|
||||||
this.$inertia.visit(newUrl, {
|
|
||||||
method: 'get',
|
|
||||||
preserveState: true,
|
|
||||||
data: {
|
|
||||||
favorite: (this.favoriteGroups?.length !== 0) ? this.favoriteGroups : "",
|
|
||||||
},
|
|
||||||
onFinish: callback, // Вызываем колбэк после завершения запроса
|
|
||||||
});
|
|
||||||
}, 500),
|
|
||||||
clearFilterFavorite(callback) {
|
|
||||||
let url = new URL(window.location.href);
|
|
||||||
// Создаем массив для хранения всех ключей, которые нужно удалить
|
|
||||||
const keysToDelete = [];
|
|
||||||
|
|
||||||
// Перебираем все параметры и добавляем ключи, начинающиеся с 'category', в массив
|
|
||||||
for (const [key] of url.searchParams) {
|
|
||||||
if (key.startsWith('favorite')) {
|
|
||||||
keysToDelete.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Удаляем все ключи из массива
|
|
||||||
keysToDelete.forEach(key => url.searchParams.delete(key));
|
|
||||||
let newUrl = url.toString();
|
|
||||||
this.$inertia.visit(newUrl, {
|
|
||||||
method: 'get',
|
|
||||||
preserveState: true,
|
|
||||||
onFinish: callback, // Вызываем колбэк после завершения запроса
|
|
||||||
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -186,47 +108,14 @@ export default {
|
|||||||
bottom-text="Просто введите название группы"
|
bottom-text="Просто введите название группы"
|
||||||
header="Расписание занятий" />
|
header="Расписание занятий" />
|
||||||
<div class="mt-7 sm:mt-12 mx-auto max-w-xl relative space-y-4">
|
<div class="mt-7 sm:mt-12 mx-auto max-w-xl relative space-y-4">
|
||||||
<!-- Form -->
|
<SearchFilter :search_filter="this.filters.search_filter" />
|
||||||
<form>
|
<div>
|
||||||
<div class="relative z-10 space-x-3 p-3 bg-white border rounded-lg shadow-lg shadow-gray-100">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div class="flex w-full">
|
|
||||||
<label for="hs-search-article-1"
|
|
||||||
class="block text-sm text-gray-700 font-medium dark:text-white">
|
|
||||||
<span class="sr-only">Поиск</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
@keydown.enter.prevent
|
|
||||||
autocomplete="off"
|
|
||||||
v-model="searchInput"
|
|
||||||
@input="search"
|
|
||||||
type="search"
|
|
||||||
id="hs-search-article-1"
|
|
||||||
class="py-2.5 px-4 block w-full border-transparent rounded-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-gray-100"
|
|
||||||
placeholder="Поиск"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="">
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<button
|
<FavoriteFilter :favorite_filter="filters.favorite_filter" :loading="loading" :favoriteGroups="favoriteGroups" />
|
||||||
@click="toggleShowFavorites"
|
<FacultyFilter :faculties="faculties" :faculty_filter="filters.faculty_filter" />
|
||||||
type="button"
|
<!-- <ScheduleListFilter>-->
|
||||||
:disabled="loading"
|
<!-- <FormEducationalFilter :forms="this.forms_education" :form-edu_filter="this.filters.form_education_filter" />-->
|
||||||
:class="
|
<!-- </ScheduleListFilter>-->
|
||||||
filters.favorite_filter.value !== null ? 'bg-primary text-white hover:bg-primary-dark': 'text-gray-700 hover:bg-gray-100',
|
|
||||||
loading ? 'animate-pulse' : ''
|
|
||||||
"
|
|
||||||
class="flex w-full py-2 px-4 items-center gap-x-2 text-xs font-medium rounded-lg border border-gray-200 focus:outline-none disabled:opacity-50 disabled:pointer-events-none"
|
|
||||||
>
|
|
||||||
<BasicIcon name="heart" class="shrink-0 size-4"/>
|
|
||||||
<span>Избранное</span>
|
|
||||||
</button>
|
|
||||||
<ScheduleListFilter>
|
|
||||||
<FormEducationalFilter :forms="this.forms_education" :form-edu_filter="this.filters.form_education_filter" />
|
|
||||||
</ScheduleListFilter>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<select v-model="faculty" @change="filter" class="py-2 px-4 pe-9 block w-full border-gray-200 rounded-lg text-xs focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none">
|
||||||
|
<option :value="null">
|
||||||
|
Выбрать факультет
|
||||||
|
</option>
|
||||||
|
<option v-for="facultyOption in faculties" :value="facultyOption.slug">{{ facultyOption.title }}</option>
|
||||||
|
</select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {Link} from "@inertiajs/vue3";
|
||||||
|
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
|
||||||
|
import {debounce} from "lodash";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "FacultyFilter",
|
||||||
|
components: {
|
||||||
|
BasicIcon,
|
||||||
|
Link,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
faculties: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
faculty_filter: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({ value: null }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
faculty: this.faculty_filter.value || null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
filter: debounce(function () {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('page');
|
||||||
|
url.searchParams.delete('form');
|
||||||
|
|
||||||
|
if (this.faculty === null) {
|
||||||
|
url.searchParams.delete('faculty');
|
||||||
|
} else {
|
||||||
|
url.searchParams.set('faculty', this.faculty);
|
||||||
|
}
|
||||||
|
|
||||||
|
let newUrl = url.toString();
|
||||||
|
|
||||||
|
this.$inertia.visit(newUrl, {
|
||||||
|
method: 'get',
|
||||||
|
preserveState: true,
|
||||||
|
});
|
||||||
|
}, 500),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
@click="toggleShowFavorites"
|
||||||
|
type="button"
|
||||||
|
:disabled="isLoading" :class="
|
||||||
|
favorite_filter.value !== null ? 'bg-primary text-white hover:bg-primary-dark': 'text-gray-700 hover:bg-gray-100',
|
||||||
|
isLoading ? 'animate-pulse' : '' "
|
||||||
|
class="flex w-full py-2 px-4 items-center gap-x-2 text-xs font-medium rounded-lg border border-gray-200 focus:outline-none disabled:opacity-50 disabled:pointer-events-none"
|
||||||
|
>
|
||||||
|
<BasicIcon name="heart" class="shrink-0 size-4"/>
|
||||||
|
<span>Избранное</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {Link} from "@inertiajs/vue3";
|
||||||
|
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
|
||||||
|
import {debounce} from "lodash";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "FavoriteFilter",
|
||||||
|
components: {
|
||||||
|
BasicIcon,
|
||||||
|
Link,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
favorite_filter: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
favoriteGroups: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// Инициализируйте локальное свойство данных для управления состоянием загрузки
|
||||||
|
isLoading: false, // Вы также можете инициализировать его на основе пропса, если это необходимо: this.initialLoadingProp || false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleShowFavorites() {
|
||||||
|
this.isLoading = true; // Теперь вы изменяете свое локальное свойство данных
|
||||||
|
|
||||||
|
if (this.favorite_filter.value === null) {
|
||||||
|
this.filterFavorite(() => {
|
||||||
|
this.isLoading = false; // Отключаем состояние загрузки после завершения
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.clearFilterFavorite(() => {
|
||||||
|
this.isLoading = false; // Отключаем состояние загрузки после завершения
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
filterFavorite: debounce(function (callback) {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
const keysToDelete = [];
|
||||||
|
|
||||||
|
for (const [key] of url.searchParams) {
|
||||||
|
if (key.startsWith('favorite')) {
|
||||||
|
keysToDelete.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keysToDelete.forEach(key => url.searchParams.delete(key));
|
||||||
|
let newUrl = url.toString();
|
||||||
|
this.$inertia.visit(newUrl, {
|
||||||
|
method: 'get',
|
||||||
|
preserveState: true,
|
||||||
|
data: {
|
||||||
|
favorite: (this.favoriteGroups?.length !== 0) ? this.favoriteGroups : "",
|
||||||
|
},
|
||||||
|
onFinish: callback,
|
||||||
|
});
|
||||||
|
}, 500),
|
||||||
|
clearFilterFavorite(callback) {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
const keysToDelete = [];
|
||||||
|
|
||||||
|
for (const [key] of url.searchParams) {
|
||||||
|
if (key.startsWith('favorite')) {
|
||||||
|
keysToDelete.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keysToDelete.forEach(key => url.searchParams.delete(key));
|
||||||
|
let newUrl = url.toString();
|
||||||
|
this.$inertia.visit(newUrl, {
|
||||||
|
method: 'get',
|
||||||
|
preserveState: true,
|
||||||
|
onFinish: callback,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<form>
|
||||||
|
<div class="relative z-10 space-x-3 px-3 py-2 bg-white border rounded-lg shadow-lg shadow-gray-100">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="flex w-full">
|
||||||
|
<label for="hs-search-article-1"
|
||||||
|
class="block text-sm text-gray-700 font-medium dark:text-white">
|
||||||
|
<span class="sr-only">Поиск</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
@keydown.enter.prevent
|
||||||
|
autocomplete="off"
|
||||||
|
v-model="searchInput"
|
||||||
|
@input="search"
|
||||||
|
type="search"
|
||||||
|
id="hs-search-article-1"
|
||||||
|
class="py-2.5 px-4 block w-full border-transparent rounded-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-gray-100"
|
||||||
|
placeholder="Поиск"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {Link} from "@inertiajs/vue3";
|
||||||
|
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
|
||||||
|
import {debounce} from "lodash";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SearchFilter",
|
||||||
|
components: {
|
||||||
|
BasicIcon,
|
||||||
|
Link,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
search_filter: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({ value: null }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchInput: this.search_filter.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
search: debounce(function () {
|
||||||
|
if(this.searchInput === "") {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('search');
|
||||||
|
let newUrl = url.toString();
|
||||||
|
this.$inertia.visit(newUrl,{
|
||||||
|
method: 'get',
|
||||||
|
preserveState: true,
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('page');
|
||||||
|
let newUrl = url.toString();
|
||||||
|
this.$inertia.visit(newUrl,{
|
||||||
|
method: 'get',
|
||||||
|
data: {
|
||||||
|
search: this.searchInput,
|
||||||
|
},
|
||||||
|
preserveState: true,
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 500),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user