diff --git a/app/Http/Controllers/Admin/PackageController.php b/app/Http/Controllers/Admin/PackageController.php index 071dc88..a3493fd 100644 --- a/app/Http/Controllers/Admin/PackageController.php +++ b/app/Http/Controllers/Admin/PackageController.php @@ -12,6 +12,7 @@ use App\Models\SmsTemplate; use App\Services\Contact\PhoneSelector; use App\Services\Sms\SmsService; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Bus; @@ -50,6 +51,7 @@ public function create(Request $request): Response ->get(['id', 'name', 'content']); $segments = \App\Models\Segment::query() ->where('active', true) + ->where('exclude', false) ->orderBy('name') ->get(['id', 'name']); // Provide a lightweight list of recent clients with person names for filtering @@ -321,7 +323,6 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat $request->validate([ 'segment_id' => ['nullable', 'integer', 'exists:segments,id'], 'q' => ['nullable', 'string'], - 'client_id' => ['nullable', 'integer', 'exists:clients,id'], 'only_mobile' => ['nullable', 'boolean'], 'only_validated' => ['nullable', 'boolean'], @@ -332,13 +333,13 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat ]); $segmentId = $request->input('segment_id') ? (int) $request->input('segment_id') : null; - $query = Contract::query() ->with([ 'clientCase.person.phones', 'clientCase.client.person', 'account', + 'segments:id,name', ]) ->select('contracts.*') ->latest('contracts.id'); @@ -350,6 +351,15 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat ->where('contract_segment.segment_id', '=', $segmentId) ->where('contract_segment.active', true); }); + } else { + // Only include contracts that have at least one active, non-excluded segment + $query->whereExists(fn ($exist) => $exist->select(\DB::raw(1)) + ->from('contract_segment') + ->join('segments', 'segments.id', '=', 'contract_segment.segment_id') + ->where('contract_segment.active', true) + ->where('segments.exclude', false) + ->whereColumn('contract_segment.contract_id', 'contracts.id') + ); } if ($q = trim((string) $request->input('q'))) { @@ -399,13 +409,14 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat }); } - $contracts = $query->get(); + $contracts = $query->limit(500)->get(); $data = collect($contracts)->map(function (Contract $contract) use ($selector) { $person = $contract->clientCase?->person; $selected = $person ? $selector->selectForPerson($person) : ['phone' => null, 'reason' => 'no_person']; $phone = $selected['phone']; $clientPerson = $contract->clientCase?->client?->person; + $segment = collect($contract->segments)->last(); return [ 'id' => $contract->id, @@ -423,6 +434,7 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat 'uuid' => $person?->uuid, 'full_name' => $person?->full_name, ], + 'segment' => $segment, // Stranka: the client person 'client' => $clientPerson ? [ 'id' => $contract->clientCase?->client?->id, @@ -440,7 +452,7 @@ public function contracts(Request $request, PhoneSelector $selector): \Illuminat }); return response()->json([ - 'data' => $data + 'data' => $data, ]); } diff --git a/resources/js/Components/app/ui/AppRangeDatePicker.vue b/resources/js/Components/app/ui/AppRangeDatePicker.vue new file mode 100644 index 0000000..7a831a5 --- /dev/null +++ b/resources/js/Components/app/ui/AppRangeDatePicker.vue @@ -0,0 +1,177 @@ + + + diff --git a/resources/js/Pages/Admin/Packages/Create.vue b/resources/js/Pages/Admin/Packages/Create.vue index 8d9b1a3..c9971db 100644 --- a/resources/js/Pages/Admin/Packages/Create.vue +++ b/resources/js/Pages/Admin/Packages/Create.vue @@ -2,6 +2,7 @@ import AdminLayout from "@/Layouts/AdminLayout.vue"; import { Link, router, useForm } from "@inertiajs/vue3"; import { ref, computed, nextTick } from "vue"; +import axios from "axios"; import { Card, CardContent, @@ -39,6 +40,9 @@ import { BadgeCheckIcon, } from "lucide-vue-next"; import { fmtDateDMY } from "@/Utilities/functions"; +import { upperFirst } from "lodash"; +import AppCombobox from "@/Components/app/ui/AppCombobox.vue"; +import AppRangeDatePicker from "@/Components/app/ui/AppRangeDatePicker.vue"; const props = defineProps({ profiles: { type: Array, default: () => [] }, @@ -123,13 +127,19 @@ const contracts = ref({ const segmentId = ref(null); const search = ref(""); const clientId = ref(null); -const startDateFrom = ref(""); -const startDateTo = ref(""); -const promiseDateFrom = ref(""); -const promiseDateTo = ref(""); +const startDateRange = ref({ start: null, end: null }); +const promiseDateRange = ref({ start: null, end: null }); const onlyMobile = ref(false); const onlyValidated = ref(false); const loadingContracts = ref(false); + +// Transform clients for AppCombobox +const clientItems = computed(() => + props.clients.map((c) => ({ + value: c.id, + label: c.name, + })) +); const selectedContractIds = ref(new Set()); const perPage = ref(25); @@ -153,6 +163,11 @@ const contractColumns = [ accessorFn: (row) => row.selected_phone?.number || "—", header: "Izbrana številka", }, + { + id: "segment", + accessorFn: (row) => upperFirst(row.segment?.name) || "—", + header: "Segment", + }, { accessorKey: "no_phone_reason", header: "Opomba" }, ]; @@ -175,19 +190,22 @@ async function loadContracts(url = null) { if (segmentId.value) params.append("segment_id", segmentId.value); if (search.value) params.append("q", search.value); if (clientId.value) params.append("client_id", clientId.value); - if (startDateFrom.value) params.append("start_date_from", startDateFrom.value); - if (startDateTo.value) params.append("start_date_to", startDateTo.value); - if (promiseDateFrom.value) params.append("promise_date_from", promiseDateFrom.value); - if (promiseDateTo.value) params.append("promise_date_to", promiseDateTo.value); + if (startDateRange.value?.start) + params.append("start_date_from", startDateRange.value.start); + if (startDateRange.value?.end) + params.append("start_date_to", startDateRange.value.end); + if (promiseDateRange.value?.start) + params.append("promise_date_from", promiseDateRange.value.start); + if (promiseDateRange.value?.end) + params.append("promise_date_to", promiseDateRange.value.end); if (onlyMobile.value) params.append("only_mobile", "1"); if (onlyValidated.value) params.append("only_validated", "1"); params.append("per_page", perPage.value); const target = url || `${route("admin.packages.contracts")}?${params.toString()}`; - const res = await fetch(target, { + const { data: json } = await axios.get(target, { headers: { "X-Requested-With": "XMLHttpRequest" }, }); - const json = await res.json(); // Wait for next tick before updating to avoid Vue reconciliation issues await nextTick(); @@ -238,10 +256,13 @@ function goToPage(page) { if (segmentId.value) params.append("segment_id", segmentId.value); if (search.value) params.append("q", search.value); if (clientId.value) params.append("client_id", clientId.value); - if (startDateFrom.value) params.append("start_date_from", startDateFrom.value); - if (startDateTo.value) params.append("start_date_to", startDateTo.value); - if (promiseDateFrom.value) params.append("promise_date_from", promiseDateFrom.value); - if (promiseDateTo.value) params.append("promise_date_to", promiseDateTo.value); + if (startDateRange.value?.start) + params.append("start_date_from", startDateRange.value.start); + if (startDateRange.value?.end) params.append("start_date_to", startDateRange.value.end); + if (promiseDateRange.value?.start) + params.append("promise_date_from", promiseDateRange.value.start); + if (promiseDateRange.value?.end) + params.append("promise_date_to", promiseDateRange.value.end); if (onlyMobile.value) params.append("only_mobile", "1"); if (onlyValidated.value) params.append("only_validated", "1"); params.append("per_page", perPage.value); @@ -255,10 +276,8 @@ function resetFilters() { segmentId.value = null; clientId.value = null; search.value = ""; - startDateFrom.value = ""; - startDateTo.value = ""; - promiseDateFrom.value = ""; - promiseDateTo.value = ""; + startDateRange.value = { start: null, end: null }; + promiseDateRange.value = { start: null, end: null }; onlyMobile.value = false; onlyValidated.value = false; contracts.value = { @@ -448,9 +467,10 @@ const numbersCount = computed(() => {
- +
@@ -586,29 +604,21 @@ const numbersCount = computed(() => {

Datum začetka pogodbe

-
-
- - -
-
- - -
-
+

Datum obljube plačila

-
-
- - -
-
- - -
-
+
@@ -621,8 +631,8 @@ const numbersCount = computed(() => {
@@ -669,7 +679,7 @@ const numbersCount = computed(() => {
- Rezultati iskanja + Rezultati iskanja (do 500 zapisov) Najdeno {{ contracts.meta.total }} {{ @@ -689,7 +699,7 @@ const numbersCount = computed(() => { variant="secondary" class="text-sm" > - + Izbrano: {{ selectedContractIds.size }}
@@ -111,10 +111,6 @@ function confirmDelete() { :meta="packages" route-name="admin.packages.index" > - - @@ -128,7 +124,9 @@ function confirmDelete() {