From 84b75143df4e5d6c7e9133de666b4166bcd79a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pocrnji=C4=8D?= Date: Sun, 28 Dec 2025 12:15:37 +0100 Subject: [PATCH] Changes to field job view and controller --- app/Http/Controllers/FieldJobController.php | 72 ++- resources/js/Pages/FieldJob/Index.vue | 473 ++++++++++++++------ resources/js/Pages/Imports/Create.vue | 39 +- 3 files changed, 411 insertions(+), 173 deletions(-) diff --git a/app/Http/Controllers/FieldJobController.php b/app/Http/Controllers/FieldJobController.php index 07a7639..92b79e5 100644 --- a/app/Http/Controllers/FieldJobController.php +++ b/app/Http/Controllers/FieldJobController.php @@ -26,10 +26,13 @@ public function index(Request $request) ])->filter()->unique()->values(); $search = $request->input('search'); + $searchAssigned = $request->input('search_assigned'); $assignedUserId = $request->input('assigned_user_id'); + $unassignedClientUuids = $request->input('unassigned_client_uuids'); + $assignedClientUuids = $request->input('assigned_client_uuids'); $unassignedContracts = Contract::query() - ->with(['clientCase.person.addresses', 'clientCase.client.person', 'type', 'account']) + ->with(['clientCase.person.addresses', 'clientCase.client.person:id,uuid,full_name', 'type', 'account']) ->when($segmentIds->isNotEmpty(), fn($q) => $q->whereHas('segments', fn($rq) => $rq->whereIn('segments.id', $segmentIds)), fn($q) => $q->whereRaw('1 = 0') @@ -45,50 +48,83 @@ public function index(Request $request) ) ) ) + ->when(!empty($unassignedClientUuids) && is_array($unassignedClientUuids), fn ($q) => + $q->whereHas('clientCase.client', fn($cq) => + $cq->whereIn('uuid', $unassignedClientUuids) + ) + ) ->whereDoesntHave('fieldJobs', fn ($q) => $q->whereNull('completed_at') ->whereNull('cancelled_at') ) - ->latest('id') - ->paginate( - $request->input('per_page_contracts', 10), - ['*'], - 'page_contracts', - $request->input('page_contracts', 1) - ); + ->latest('id'); + $unassignedClients = $unassignedContracts->get() + ->pluck('clientCase.client') + ->filter() + ->unique('id'); $assignedContracts = Contract::query() - ->with(['clientCase.person.addresses', 'clientCase.client.person', 'type', 'account', 'lastFieldJobs', 'lastFieldJobs.assignedUser', 'lastFieldJobs.user']) + ->with(['clientCase.person.addresses', 'clientCase.client.person:id,uuid,full_name', 'type', 'account', 'lastFieldJobs', 'lastFieldJobs.assignedUser', 'lastFieldJobs.user']) ->when($segmentIds->isNotEmpty(), fn($q) => $q->whereHas('segments', fn($rq) => $rq->whereIn('segments.id', $segmentIds)), fn($q) => $q->whereRaw('1 = 0') ) + ->when( !empty($searchAssigned), fn ($q) => + $q->where(fn($sq) => + $sq->where('reference', 'like', "%{$searchAssigned}%") + ->orWhereHas('clientCase.person', fn($psq) => + $psq->where('full_name', 'ilike', "%{$searchAssigned}%") + ) + ->orWhereHas('clientCase.person.addresses', fn ($ccpaq) => + $ccpaq->where('address', 'ilike', "%{$searchAssigned}") + ) + ) + ) + ->when(!empty($assignedClientUuids) && is_array($assignedClientUuids), fn ($q) => + $q->whereHas('clientCase.client', fn($cq) => + $cq->whereIn('uuid', $assignedClientUuids) + ) + ) ->whereHas('lastFieldJobs', fn ($q) => $q->whereNull('completed_at') ->whereNull('cancelled_at') ->when($assignedUserId && $assignedUserId !== 'all', fn ($jq) => $jq->where('assigned_user_id', $assignedUserId)) ) - ->latest('id') - ->paginate( - $request->input('per_page_assignments', 10), - ['*'], - 'page_assignments', - $request->input('page_assignments', 1) - ); + ->latest('id'); + + $assignedClients = $assignedContracts->get() + ->pluck('clientCase.client') + ->filter() + ->unique('id'); $users = User::query()->orderBy('name')->get(['id', 'name']); return Inertia::render('FieldJob/Index', [ 'setting' => $setting, - 'unassignedContracts' => $unassignedContracts, - 'assignedContracts' => $assignedContracts, + 'unassignedContracts' => $unassignedContracts->paginate( + $request->input('per_page_contracts', 10), + ['*'], + 'page_contracts', + $request->input('page_contracts', 1) + ), + 'assignedContracts' => $assignedContracts->paginate( + $request->input('per_page_assignments', 10), + ['*'], + 'page_assignments', + $request->input('page_assignments', 1) + ), + 'unassignedClients' => $unassignedClients, + 'assignedClients' => $assignedClients, 'users' => $users, 'filters' => [ 'search' => $search, + 'search_assigned' => $searchAssigned, 'assigned_user_id' => $assignedUserId, + 'unassigned_client_uuids' => $unassignedClientUuids, + 'assigned_client_uuids' => $assignedClientUuids, ], ]); } diff --git a/resources/js/Pages/FieldJob/Index.vue b/resources/js/Pages/FieldJob/Index.vue index 3b50323..b9ccf58 100644 --- a/resources/js/Pages/FieldJob/Index.vue +++ b/resources/js/Pages/FieldJob/Index.vue @@ -3,7 +3,7 @@ import AppLayout from "@/Layouts/AppLayout.vue"; import { Link, useForm, router } from "@inertiajs/vue3"; import { computed, ref, watch } from "vue"; import DataTable from "@/Components/DataTable/DataTableNew2.vue"; -import { Card } from "@/Components/ui/card"; +import { Card, CardTitle } from "@/Components/ui/card"; import { Input } from "@/Components/ui/input"; import { Button } from "@/Components/ui/button"; import { Label } from "@/Components/ui/label"; @@ -15,19 +15,45 @@ import { SelectTrigger, SelectValue, } from "@/Components/ui/select"; -import { AlertCircle } from "lucide-vue-next"; +import { AlertCircle, FileCheckCornerIcon, FilesIcon, Filter } from "lucide-vue-next"; import Checkbox from "@/Components/ui/checkbox/Checkbox.vue"; import Pagination from "@/Components/Pagination.vue"; import { watchDebounced } from "@vueuse/core"; +import Dropdown from "@/Components/Dropdown.vue"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/Components/ui/dropdown-menu"; +import AppPopover from "@/Components/app/ui/AppPopover.vue"; +import InputLabel from "@/Components/InputLabel.vue"; +import AppMultiSelect from "@/Components/app/ui/AppMultiSelect.vue"; +import AppCard from "@/Components/app/ui/card/AppCard.vue"; const props = defineProps({ setting: Object, unassignedContracts: Object, assignedContracts: Object, users: Array, + unassignedClients: Array, + assignedClients: Array, filters: Object, }); +const filterUnassignedContracts = ref(false); +const filterAssignedContracts = ref(false); +const filterUnassignedSelectedClient = ref( + Array.isArray(props.filters?.unassigned_client_uuids) + ? props.filters.unassigned_client_uuids + : [] +); +const filterAssignedSelectedClient = ref( + Array.isArray(props.filters?.assigned_client_uuids) + ? props.filters.assigned_client_uuids + : [] +); + const form = useForm({ contract_uuid: null, assigned_user_id: null, @@ -85,6 +111,7 @@ function toggleContractSelection(uuid, checked) { // Initialize search and filter from URL params const search = ref(props.filters?.search || ""); +const searchAssigned = ref(props.filters?.search_assigned || ""); const assignedFilterUserId = ref(props.filters?.assigned_user_id || "all"); // Navigation helpers @@ -128,6 +155,37 @@ watchDebounced( } ); +const applySearchAssigned = async function () { + const params = Object.fromEntries( + new URLSearchParams(window.location.search).entries() + ); + + const term = (searchAssigned.value || "").trim(); + if (term) { + params.search_assigned = term; + } else { + delete params.search_assigned; + } + delete params.page_assignments; + router.get(route("fieldjobs.index"), params, { + preserveState: true, + replace: true, + preserveScroll: true, + only: ["assignedContracts", "filters"], + }); +}; + +watchDebounced( + () => searchAssigned.value, + (val) => { + applySearchAssigned(); + }, + { + debounce: 200, + maxWait: 1000, + } +); + // Watch search and filter changes /*watch(search, (value) => { navigateWithParams({ @@ -143,6 +201,44 @@ watch(assignedFilterUserId, (value) => { navigateWithParams({ search: search.value || undefined, assigned_user_id: value !== "all" ? value : undefined, + unassigned_client_uuids: + filterUnassignedSelectedClient.value?.length > 0 + ? filterUnassignedSelectedClient.value + : undefined, + assigned_client_uuids: + filterAssignedSelectedClient.value?.length > 0 + ? filterAssignedSelectedClient.value + : undefined, + page_contracts: props.unassignedContracts?.current_page, + page_assignments: 1, // Reset to first page on filter change + }); +}); + +watch(filterUnassignedSelectedClient, (value) => { + navigateWithParams({ + search: search.value || undefined, + assigned_user_id: + assignedFilterUserId.value !== "all" ? assignedFilterUserId.value : undefined, + unassigned_client_uuids: value?.length > 0 ? value : undefined, + assigned_client_uuids: + filterAssignedSelectedClient.value?.length > 0 + ? filterAssignedSelectedClient.value + : undefined, + page_contracts: 1, // Reset to first page on filter change + page_assignments: props.assignedContracts?.current_page, + }); +}); + +watch(filterAssignedSelectedClient, (value) => { + navigateWithParams({ + search: search.value || undefined, + assigned_user_id: + assignedFilterUserId.value !== "all" ? assignedFilterUserId.value : undefined, + unassigned_client_uuids: + filterUnassignedSelectedClient.value?.length > 0 + ? filterUnassignedSelectedClient.value + : undefined, + assigned_client_uuids: value?.length > 0 ? value : undefined, page_contracts: props.unassignedContracts?.current_page, page_assignments: 1, // Reset to first page on filter change }); @@ -248,7 +344,7 @@ const unassignedRows = computed(() => ? null : Number(c.account.balance_amount), case_person: c.client_case?.person?.full_name || null, - client_person: c.client?.person?.full_name || null, + client_person: c.client_case?.client?.person?.full_name || null, address: primaryCaseAddress(c) || null, })) ); @@ -261,7 +357,7 @@ const assignedRows = computed(() => ? null : Number(c.account.balance_amount), case_person: c.client_case?.person?.full_name || null, - client_person: c.client?.person?.full_name || null, + client_person: c.client_case?.client?.person?.full_name || null, address: primaryCaseAddress(c) || null, assigned_to: c.last_field_jobs.assigned_user.name || null, assigned_at_formatted: formatDate(c.last_field_jobs.assigned_at), @@ -272,130 +368,181 @@ const assignedRows = computed(() =>