diff --git a/app/app/Http/Controllers/Api/ProjectController.php b/app/app/Http/Controllers/Api/ProjectController.php index b56428d1..ffc61aa3 100644 --- a/app/app/Http/Controllers/Api/ProjectController.php +++ b/app/app/Http/Controllers/Api/ProjectController.php @@ -68,8 +68,41 @@ class ProjectController extends Controller }); } - $perPage = min((int) $request->query('per_page', '20'), 100); - $projects = $query->orderBy('created_at', 'desc')->paginate($perPage); + // #7: фильтр по региону (subject code 1..89). Проект под фильтр попадает, если + // его regions[] содержит код ИЛИ пуст (= вся РФ, имплицитно покрывает любой регион). + $region = (int) $request->query('region', '0'); + if ($region >= 1 && $region <= 89) { + $query->where(function ($q) use ($region) { + $q->whereRaw('regions @> ARRAY[?]::int[]', [$region]) + ->orWhereRaw("regions = '{}'::int[]") + ->orWhereNull('regions'); + }); + } + + // #7: фильтр по дню недели приёма (0=Пн..6=Вс — same bit-index, как в UI dayLabels). + $day = $request->query('delivery_day'); + if ($day !== null && $day !== '' && (int) $day >= 0 && (int) $day <= 6) { + $bit = 1 << (int) $day; + $query->whereRaw('(delivery_days_mask & ?) <> 0', [$bit]); + } + + // #7: сортировка. Whitelist + опциональный '-' для desc. Default = '-delivered_today' + // (карточки с активной доставкой за сегодня видны сверху, как просил заказчик). + $sortRaw = (string) $request->query('sort', '-delivered_today'); + $desc = str_starts_with($sortRaw, '-'); + $sortField = ltrim($sortRaw, '-'); + $sortable = ['delivered_today', 'delivered_in_month', 'daily_limit_target', 'name', 'created_at']; + if (! in_array($sortField, $sortable, true)) { + $sortField = 'delivered_today'; + $desc = true; + } + + // #6: per_page до 200 (было 100). UI-селектор: 20/50/100/200. + $perPage = min((int) $request->query('per_page', '20'), 200); + $projects = $query + ->orderBy($sortField, $desc ? 'desc' : 'asc') + ->orderBy('id', 'desc') // стабильный tie-breaker для пагинации + ->paginate($perPage); return response()->json([ 'data' => ProjectResource::collection($projects->items()), diff --git a/app/resources/js/components/projects/ProjectDetailsDrawer.vue b/app/resources/js/components/projects/ProjectDetailsDrawer.vue index 81195bfb..7d5cd412 100644 --- a/app/resources/js/components/projects/ProjectDetailsDrawer.vue +++ b/app/resources/js/components/projects/ProjectDetailsDrawer.vue @@ -58,6 +58,8 @@ const store = useProjectsStore(); async function onPause(): Promise { if (!props.project) return; await store.toggleActive(props.project); + // #4: после паузы/возобновления панель и галочка должны исчезнуть (как у Save и Delete). + emit('close'); } async function onDelete(): Promise { diff --git a/app/resources/js/stores/projectsStore.ts b/app/resources/js/stores/projectsStore.ts index bdd9281e..462597fe 100644 --- a/app/resources/js/stores/projectsStore.ts +++ b/app/resources/js/stores/projectsStore.ts @@ -24,7 +24,26 @@ export interface Project { export const useProjectsStore = defineStore('projects', () => { const items = ref([]); const total = ref(0); - const filters = reactive({ signal_type: '', status: '', search: '', page: 1, per_page: 20 }); + // #6/#7: per_page 20/50/100/200, sort ('-delivered_today' default), region (subject code 1..89), delivery_day (0..6). + const filters = reactive<{ + signal_type: string; + status: string; + search: string; + page: number; + per_page: number; + sort: string; + region: number | null; + delivery_day: number | null; + }>({ + signal_type: '', + status: '', + search: '', + page: 1, + per_page: 20, + sort: '-delivered_today', + region: null, + delivery_day: null, + }); const selectedIds = ref>(new Set()); const pendingIds = ref>(new Set()); const loading = ref(false); @@ -43,6 +62,9 @@ export const useProjectsStore = defineStore('projects', () => { if (filters.signal_type) params.signal_type = filters.signal_type; if (filters.status) params.status = filters.status; if (filters.search) params.search = filters.search; + if (filters.sort) params.sort = filters.sort; + if (filters.region !== null) params.region = filters.region; + if (filters.delivery_day !== null) params.delivery_day = filters.delivery_day; const { data } = await axios.get('/api/projects', { params }); items.value = data.data; total.value = data.meta.total; @@ -140,7 +162,7 @@ export const useProjectsStore = defineStore('projects', () => { // Watch filter changes — clear selection on switch watch( - () => [filters.signal_type, filters.status, filters.search] as const, + () => [filters.signal_type, filters.status, filters.search, filters.region, filters.delivery_day] as const, () => { clearSelection(); selectAllByFilter.value = false; diff --git a/app/resources/js/views/ProjectsView.vue b/app/resources/js/views/ProjectsView.vue index c9f64dbd..da2ce9e1 100644 --- a/app/resources/js/views/ProjectsView.vue +++ b/app/resources/js/views/ProjectsView.vue @@ -1,5 +1,5 @@