Major change update laravel, inertia v2 -> v3, other changes
This commit is contained in:
parent
92f54f7103
commit
054202dc32
|
|
@ -71,10 +71,8 @@ public function index(ClientCase $clientCase, Request $request)
|
|||
$que->whereDate('client_cases.created_at', '<=', $to);
|
||||
})
|
||||
->groupBy('client_cases.id')
|
||||
->addSelect([
|
||||
\DB::raw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count'),
|
||||
\DB::raw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum'),
|
||||
])
|
||||
->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count')
|
||||
->selectRaw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum')
|
||||
->with(['person.client', 'client.person'])
|
||||
->orderByDesc('client_cases.created_at');
|
||||
|
||||
|
|
@ -915,8 +913,8 @@ public function show(ClientCase $clientCase)
|
|||
->get(),
|
||||
'sms_senders' => \App\Models\SmsSender::query()
|
||||
->select(['id', 'profile_id'])
|
||||
->addSelect(\DB::raw('sname as name'))
|
||||
->addSelect(\DB::raw('phone_number as phone'))
|
||||
->selectRaw('sname as name')
|
||||
->selectRaw('phone_number as phone')
|
||||
->orderBy('sname')
|
||||
->get(),
|
||||
'sms_templates' => \App\Models\SmsTemplate::query()
|
||||
|
|
|
|||
|
|
@ -40,12 +40,8 @@ public function index(Client $client, Request $request)
|
|||
})
|
||||
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
||||
->groupBy('clients.id')
|
||||
->addSelect([
|
||||
// Number of client cases for this client that have at least one active contract
|
||||
DB::raw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN client_cases.id END) as cases_with_active_contracts_count'),
|
||||
// Sum of account balances for active contracts
|
||||
DB::raw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum'),
|
||||
])
|
||||
->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN client_cases.id END) as cases_with_active_contracts_count')
|
||||
->selectRaw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum')
|
||||
->with('person')
|
||||
->orderByDesc('clients.created_at');
|
||||
|
||||
|
|
@ -89,10 +85,8 @@ public function show(Client $client, Request $request)
|
|||
})
|
||||
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
||||
->groupBy('client_cases.id')
|
||||
->addSelect([
|
||||
\DB::raw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count'),
|
||||
\DB::raw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum'),
|
||||
])
|
||||
->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count')
|
||||
->selectRaw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum')
|
||||
->with(['person', 'client.person'])
|
||||
->where('client_cases.active', 1)
|
||||
->orderByDesc('client_cases.created_at')
|
||||
|
|
|
|||
|
|
@ -100,13 +100,13 @@ public function __invoke(SmsService $sms): Response
|
|||
// Field jobs assigned today - cached
|
||||
$fieldJobsAssignedToday = Cache::remember('dashboard:field_jobs_assigned_today:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) {
|
||||
return FieldJob::query()
|
||||
->whereDate(DB::raw('COALESCE(assigned_at, created_at)'), $today)
|
||||
->whereRaw('DATE(COALESCE(assigned_at, created_at)) = ?', [$today->toDateString()])
|
||||
->select(['id', 'assigned_user_id', 'priority', 'assigned_at', 'created_at', 'contract_id'])
|
||||
->with(['contract' => function ($q) {
|
||||
$q->select('id', 'uuid', 'reference', 'client_case_id')
|
||||
->with(['clientCase:id,uuid,person_id', 'clientCase.person:id,full_name', 'segments:id,name']);
|
||||
}])
|
||||
->latest(DB::raw('COALESCE(assigned_at, created_at)'))
|
||||
->orderByRaw('COALESCE(assigned_at, created_at) DESC')
|
||||
->limit(15)
|
||||
->get()
|
||||
->map(function ($fj) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ public function index(Request $request)
|
|||
'current_page' => $paginator->currentPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'links' => $paginator->linkCollection()->toArray(),
|
||||
'path' => $paginator->path(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'to' => $paginator->lastItem(),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ public function update(Person $person, Request $request)
|
|||
'tax_number' => 'nullable|integer',
|
||||
'social_security_number' => 'nullable|integer',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'employer' => 'nullable|string|max:255',
|
||||
'birthday' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$person->update($attributes);
|
||||
|
|
|
|||
|
|
@ -17,11 +17,6 @@ public function index(Request $request): \Inertia\Response
|
|||
$search = $request->input('search');
|
||||
$clientFilter = $request->input('client');
|
||||
|
||||
// On full page loads, always start from page 1
|
||||
if (! $request->header('X-Inertia-Partial-Data')) {
|
||||
$request->merge(['pending' => 1, 'processed' => 1]);
|
||||
}
|
||||
|
||||
$eagerLoad = [
|
||||
'contract' => function ($q) {
|
||||
$q->with([
|
||||
|
|
@ -85,8 +80,8 @@ public function index(Request $request): \Inertia\Response
|
|||
->values();
|
||||
|
||||
return Inertia::render('Phone/Index', [
|
||||
'pendingJobs' => $pendingQuery->paginate(15, pageName: 'pending'),
|
||||
'processedJobs' => $processedQuery->paginate(15, pageName: 'processed'),
|
||||
'pendingJobs' => Inertia::scroll(fn () => $pendingQuery->paginate(15, pageName: 'pending')),
|
||||
'processedJobs' => Inertia::scroll(fn () => $processedQuery->paginate(15, pageName: 'processed')),
|
||||
'clients' => $clients,
|
||||
'view_mode' => 'assigned',
|
||||
'filters' => [
|
||||
|
|
@ -102,11 +97,6 @@ public function completedToday(Request $request): \Inertia\Response
|
|||
$search = $request->input('search');
|
||||
$clientFilter = $request->input('client');
|
||||
|
||||
// On full page loads, always start from page 1
|
||||
if (! $request->header('X-Inertia-Partial-Data')) {
|
||||
$request->merge(['completed' => 1]);
|
||||
}
|
||||
|
||||
$start = now()->startOfDay();
|
||||
$end = now()->endOfDay();
|
||||
|
||||
|
|
@ -166,7 +156,7 @@ public function completedToday(Request $request): \Inertia\Response
|
|||
->values();
|
||||
|
||||
return Inertia::render('Phone/Index', [
|
||||
'completedJobs' => $query->paginate(15, pageName: 'completed'),
|
||||
'completedJobs' => Inertia::scroll(fn () => $query->paginate(15, pageName: 'completed')),
|
||||
'clients' => $clients,
|
||||
'view_mode' => 'completed-today',
|
||||
'filters' => [
|
||||
|
|
|
|||
|
|
@ -10,21 +10,21 @@
|
|||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"diglactic/laravel-breadcrumbs": "^10.0",
|
||||
"http-interop/http-factory-guzzle": "^1.2",
|
||||
"inertiajs/inertia-laravel": "^2.0",
|
||||
"laravel/framework": "12.0",
|
||||
"inertiajs/inertia-laravel": "^3.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/jetstream": "^5.2",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/scout": "^10.11",
|
||||
"laravel/tinker": "^2.9",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"meilisearch/meilisearch-php": "^1.11",
|
||||
"robertboes/inertia-breadcrumbs": "dev-laravel-12",
|
||||
"robertboes/inertia-breadcrumbs": "^1.0",
|
||||
"tightenco/ziggy": "^2.0",
|
||||
"tijsverkoyen/css-to-inline-styles": "^2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/boost": "^1.1",
|
||||
"laravel/boost": "^2.2",
|
||||
"laravel/pint": "^1.13",
|
||||
"laravel/sail": "^1.26",
|
||||
"mockery/mockery": "^1.6",
|
||||
|
|
|
|||
1777
composer.lock
generated
1777
composer.lock
generated
File diff suppressed because it is too large
Load Diff
162
package-lock.json
generated
162
package-lock.json
generated
|
|
@ -46,7 +46,7 @@
|
|||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inertiajs/vue3": "2.0",
|
||||
"@inertiajs/vue3": "^3.0",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
|
|
@ -952,26 +952,35 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@inertiajs/core": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.0.17.tgz",
|
||||
"integrity": "sha512-tvYoqiouQSJrP7i7zVq61yyuEjlL96UU4nkkOWtOajXZlubGN4XrgRpnygpDk1KBO8V2yBab3oUZm+aZImwTHg==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-3.0.3.tgz",
|
||||
"integrity": "sha512-/4sW/cfNpvujjVOZlB5UNypLGNySs7X7V8IMLNSK8+3j1KsUYGS5wpLd9EqAu8wy8RiW7PPra2rPwB6Lx/ACow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.8.2",
|
||||
"es-toolkit": "^1.34.1",
|
||||
"qs": "^6.9.0"
|
||||
"@jridgewell/trace-mapping": "^0.3.31",
|
||||
"es-toolkit": "^1.33.0",
|
||||
"laravel-precognition": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"axios": "^1.13.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"axios": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@inertiajs/vue3": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.0.17.tgz",
|
||||
"integrity": "sha512-Al0IMHQSj5aTQBLUAkljFEMCw4YRwSiOSKzN8LAbvJpKwvJFgc/wSj3wVVpr/AO9y9mz1w2mtvjnDoOzsntPLw==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-3.0.3.tgz",
|
||||
"integrity": "sha512-bhJN+GS66g1tYH1p6flKkG1N8oaT5J7ZLqBkavN9mHC6bVfoQCUG6sCuA07WTDfo9tDaxU89wsSSAf4mhn3SuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inertiajs/core": "2.0.17",
|
||||
"es-toolkit": "^1.33.0"
|
||||
"@inertiajs/core": "3.0.3",
|
||||
"es-toolkit": "^1.33.0",
|
||||
"laravel-precognition": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
|
|
@ -3804,9 +3813,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz",
|
||||
"integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==",
|
||||
"version": "1.45.1",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
|
||||
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
|
@ -4372,6 +4381,24 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-precognition": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-2.0.0.tgz",
|
||||
"integrity": "sha512-dmA4HGc9m+TsVNsJs9/XQBI8u6j7coilN+qKkBuhuXQzH3HypwS/c5dFQ4UqUGjBbcxIM7zdk91kM/SRZwIvWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-toolkit": "^1.32.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"axios": "^1.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"axios": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz",
|
||||
|
|
@ -4875,19 +4902,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/object-is": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
|
||||
|
|
@ -5098,22 +5112,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/quickselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
||||
|
|
@ -5361,82 +5359,6 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/skema": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/skema/-/skema-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inertiajs/vue3": "2.0",
|
||||
"@inertiajs/vue3": "^3.0",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Card, CardContent } from "@/Components/ui/card";
|
|||
const props = defineProps({
|
||||
label: String,
|
||||
value: [String, Number],
|
||||
icon: Object,
|
||||
icon: [Object, Function],
|
||||
iconBg: {
|
||||
type: String,
|
||||
default: "bg-primary/10",
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ function formatDateTimeNoSeconds(value) {
|
|||
last_page: imports?.meta?.last_page,
|
||||
from: imports?.meta?.from,
|
||||
to: imports?.meta?.to,
|
||||
links: imports?.links,
|
||||
links: imports?.meta?.links,
|
||||
}"
|
||||
route-name="imports.index"
|
||||
:only-props="['imports']"
|
||||
|
|
|
|||
|
|
@ -163,9 +163,7 @@ const props = defineProps({
|
|||
|
||||
<template>
|
||||
<AppLayout title="Uvozne predloge">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Uvozne predloge</h2>
|
||||
</template>
|
||||
<template #header> </template>
|
||||
|
||||
<div class="py-6">
|
||||
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
||||
|
|
|
|||
|
|
@ -56,9 +56,6 @@ import {
|
|||
Download,
|
||||
Eye,
|
||||
Building2,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Activity,
|
||||
} from "lucide-vue-next";
|
||||
|
||||
|
|
@ -284,16 +281,11 @@ const clientSummary = computed(() => {
|
|||
<template #header>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="router.visit(route('phone.index'))"
|
||||
class="shrink-0"
|
||||
>
|
||||
<ArrowLeft class="w-4 h-4 mr-1" />
|
||||
<Button variant="outline" size="sm" @click="router.visit(route('phone.index'))">
|
||||
<ArrowLeft />
|
||||
Nazaj
|
||||
</Button>
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-100 truncate">
|
||||
<h2 class="font-semibold text-gray-800 dark:text-gray-100 truncate">
|
||||
{{ client_case?.person?.full_name }}
|
||||
</h2>
|
||||
</div>
|
||||
|
|
@ -303,7 +295,7 @@ const clientSummary = computed(() => {
|
|||
variant="secondary"
|
||||
class="bg-emerald-100 text-emerald-700 hover:bg-emerald-100"
|
||||
>
|
||||
<CheckCircle2 class="w-3 h-3 mr-1" />
|
||||
<CheckCircle2 class="w-4 h-4" />
|
||||
Zaključeno danes
|
||||
</Badge>
|
||||
<Button
|
||||
|
|
@ -311,25 +303,25 @@ const clientSummary = computed(() => {
|
|||
@click="confirmComplete = true"
|
||||
class="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
<CheckCircle2 class="w-4 h-4 mr-2" />
|
||||
<CheckCircle2 class="w-4 h-4" />
|
||||
Zaključi
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="py-4 sm:py-6">
|
||||
<div class="py-4 sm:py-2">
|
||||
<div class="mx-auto max-w-5xl px-2 sm:px-4 space-y-4">
|
||||
<!-- Client details (account holder) -->
|
||||
<Card class="gap-3">
|
||||
<CardHeader>
|
||||
<Card class="p-0 py-3 gap-3">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<Building2 class="w-5 h-5 text-gray-500" />
|
||||
<span class="truncate">{{ clientSummary.name }}</span>
|
||||
<Badge variant="secondary">Naročnik</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent class="px-3">
|
||||
<Separator class="mb-4" />
|
||||
<PersonDetailPhone
|
||||
:types="types"
|
||||
|
|
@ -340,8 +332,8 @@ const clientSummary = computed(() => {
|
|||
</Card>
|
||||
|
||||
<!-- Person (case person) -->
|
||||
<Card class="gap-3">
|
||||
<CardHeader class="px-3">
|
||||
<Card class="p-0 py-3 gap-3">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<CardTitle class="flex items-center gap-2 text-base">
|
||||
<User class="w-5 h-5 text-gray-500" />
|
||||
<span class="truncate">{{ client_case.person.full_name }}</span>
|
||||
|
|
@ -353,6 +345,13 @@ const clientSummary = computed(() => {
|
|||
>
|
||||
{{ client_case.person.description }}
|
||||
</CardDescription>
|
||||
<p
|
||||
v-if="client_case?.person?.employer"
|
||||
class="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1 mt-1"
|
||||
>
|
||||
<Building2 class="w-3.5 h-3.5 shrink-0" />
|
||||
{{ client_case.person.employer }}
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent class="px-3">
|
||||
<Separator class="mb-4" />
|
||||
|
|
@ -365,21 +364,21 @@ const clientSummary = computed(() => {
|
|||
</Card>
|
||||
|
||||
<!-- Contracts assigned to me -->
|
||||
<Card class="p-0 pt-3 gap-1">
|
||||
<CardHeader class="px-4">
|
||||
<Card class="p-0 py-3 gap-1">
|
||||
<CardHeader class="px-3 py-2 pb-0">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<FileText class="w-5 h-5" />
|
||||
Pogodbe
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="p-2">
|
||||
<CardContent class="p-2 space-y-1">
|
||||
<Card
|
||||
v-for="c in contracts"
|
||||
:key="c.uuid || c.id"
|
||||
class="overflow-hidden p-0 gap-3"
|
||||
class="overflow-hidden p-0 gap-2"
|
||||
>
|
||||
<!-- Contract header: reference + type badge -->
|
||||
<CardHeader class="p-3 pb-2">
|
||||
<CardHeader class="p-3 pb-2 gap-0">
|
||||
<div class="flex items-center flex-wrap">
|
||||
<CardTitle class="text-base font-semibold">
|
||||
{{ c.reference || "Šifra pogodbe ni določena" }}
|
||||
|
|
@ -408,12 +407,20 @@ const clientSummary = computed(() => {
|
|||
<!-- Collapsibles: description, meta, last object -->
|
||||
<CardContent
|
||||
v-if="
|
||||
c.description || c.last_object || (c.meta && Object.keys(c.meta).length)
|
||||
c.description ||
|
||||
c.latest_object ||
|
||||
(c.meta && Object.keys(c.meta).length)
|
||||
"
|
||||
class="pt-0 px-0 space-y-0"
|
||||
>
|
||||
<!-- Description + Meta Accordion -->
|
||||
<template v-if="c.description || (c.meta && Object.keys(c.meta).length)">
|
||||
<!-- Description + Meta + Latest Object Accordion -->
|
||||
<template
|
||||
v-if="
|
||||
c.description ||
|
||||
(c.meta && Object.keys(c.meta).length) ||
|
||||
c.latest_object
|
||||
"
|
||||
>
|
||||
<Separator />
|
||||
<Accordion type="multiple" class="w-full">
|
||||
<AccordionItem
|
||||
|
|
@ -479,43 +486,48 @@ const clientSummary = computed(() => {
|
|||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</template>
|
||||
|
||||
<!-- Last object -->
|
||||
<template v-if="c.last_object">
|
||||
<Separator
|
||||
class="mb-3"
|
||||
:class="
|
||||
c.description || (c.meta && Object.keys(c.meta).length)
|
||||
? 'mt-2'
|
||||
: 'mt-0'
|
||||
"
|
||||
/>
|
||||
<div class="py-1 space-y-1">
|
||||
<p class="text-xs text-gray-400 uppercase tracking-wide font-medium">
|
||||
Zadnji predmet
|
||||
</p>
|
||||
<p class="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{{ c.last_object.name || c.last_object.reference }}
|
||||
<span
|
||||
v-if="c.last_object.type"
|
||||
class="ml-1.5 text-xs font-normal text-gray-400"
|
||||
>({{ c.last_object.type }})</span
|
||||
>
|
||||
</p>
|
||||
<p
|
||||
v-if="c.last_object.description"
|
||||
class="text-xs text-gray-500 dark:text-gray-400"
|
||||
<AccordionItem
|
||||
v-if="c.latest_object"
|
||||
value="latest_object"
|
||||
class="border-b-0"
|
||||
:class="
|
||||
c.description || (c.meta && Object.keys(c.meta).length)
|
||||
? 'border-t'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ c.last_object.description }}
|
||||
</p>
|
||||
</div>
|
||||
<AccordionTrigger
|
||||
class="px-3 py-2 text-xs font-medium uppercase tracking-wide hover:no-underline"
|
||||
>
|
||||
Zadnji predmet
|
||||
</AccordionTrigger>
|
||||
<AccordionContent class="px-3 pb-3">
|
||||
<div
|
||||
class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line rounded-lg bg-gray-50 dark:bg-gray-800/50 px-3 py-2.5"
|
||||
>
|
||||
<p class="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{{ c.latest_object.name || c.latest_object.reference }}
|
||||
<span
|
||||
v-if="c.latest_object.type"
|
||||
class="ml-1.5 text-xs font-normal text-gray-400"
|
||||
>({{ c.latest_object.type }})</span
|
||||
>
|
||||
</p>
|
||||
<p
|
||||
v-if="c.latest_object.description"
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1"
|
||||
>
|
||||
{{ c.latest_object.description }}
|
||||
</p>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</template>
|
||||
</CardContent>
|
||||
|
||||
<!-- Action buttons: full-width row at bottom -->
|
||||
<div class="grid grid-cols-2 gap-0 border-t mt-1">
|
||||
<div class="grid grid-cols-2 gap-0 border-t mt-0">
|
||||
<button
|
||||
class="flex items-center justify-center gap-2 py-3 text-sm font-medium text-primary hover:bg-primary/5 active:bg-primary/10 transition-colors border-r"
|
||||
@click="openDrawerAddActivity(c)"
|
||||
|
|
@ -542,27 +554,27 @@ const clientSummary = computed(() => {
|
|||
</Card>
|
||||
|
||||
<!-- Activities -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card class="p-0 py-2 gap-2">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Activity class="w-5 h-5" />
|
||||
Aktivnosti
|
||||
</CardTitle>
|
||||
<Button size="sm" @click="openDrawerAddActivity()">
|
||||
<Plus class="w-4 h-4 mr-1" />
|
||||
<Plus class="w-4 h-4" />
|
||||
Nova
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<CardContent class="space-y-1 px-2">
|
||||
<Card
|
||||
v-for="a in activities"
|
||||
:key="a.id"
|
||||
class="bg-gray-50/70 dark:bg-gray-800/50"
|
||||
class="bg-gray-50/70 dark:bg-gray-800/50 p-0 py-2 gap-2"
|
||||
>
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<div class="flex items-start justify-between">
|
||||
<CardTitle class="text-sm font-medium truncate">
|
||||
{{ activityActionLine(a) || "Aktivnost" }}
|
||||
</CardTitle>
|
||||
|
|
@ -583,7 +595,7 @@ const clientSummary = computed(() => {
|
|||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="pt-0 space-y-2">
|
||||
<CardContent class="p-2 pt-0 space-y-2">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<Badge v-if="a.contract" variant="secondary" class="text-[10px]">
|
||||
<FileText class="w-3 h-3 mr-1" />
|
||||
|
|
@ -609,7 +621,10 @@ const clientSummary = computed(() => {
|
|||
{{ a.status }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p v-if="a.note" class="text-sm text-gray-700 dark:text-gray-300">
|
||||
<p
|
||||
v-if="a.note"
|
||||
class="text-sm text-gray-900 dark:text-gray-300 whitespace-pre-line rounded-lg bg-secondary dark:bg-gray-800/50 p-2"
|
||||
>
|
||||
{{ a.note }}
|
||||
</p>
|
||||
</CardContent>
|
||||
|
|
@ -624,8 +639,8 @@ const clientSummary = computed(() => {
|
|||
</Card>
|
||||
|
||||
<!-- Documents (case + assigned contracts) -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card class="p-0 py-2 gap-2">
|
||||
<CardHeader class="px-3 py-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<FileText class="w-5 h-5" />
|
||||
|
|
@ -649,7 +664,7 @@ const clientSummary = computed(() => {
|
|||
{{ d.name || d.original_name }}
|
||||
</div>
|
||||
<div
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1 flex items-center gap-2"
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1 flex items-center gap-2 flex-wrap"
|
||||
>
|
||||
<Badge
|
||||
v-if="d.contract_reference"
|
||||
|
|
@ -659,6 +674,11 @@ const clientSummary = computed(() => {
|
|||
Pogodba: {{ d.contract_reference }}
|
||||
</Badge>
|
||||
<Badge v-else variant="outline" class="text-[10px]"> Primer </Badge>
|
||||
<span
|
||||
v-if="d.mime_type"
|
||||
class="text-[10px] text-gray-400 font-mono"
|
||||
>{{ d.mime_type }}</span
|
||||
>
|
||||
<span v-if="d.created_at" class="flex items-center gap-1">
|
||||
<Calendar class="w-3 h-3" />
|
||||
{{ new Date(d.created_at).toLocaleDateString("sl-SI") }}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,16 @@ import {
|
|||
} from "@/Components/ui/select";
|
||||
import { Skeleton } from "@/Components/ui/skeleton";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
|
||||
import { router } from "@inertiajs/vue3";
|
||||
import { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { InfiniteScroll, router } from "@inertiajs/vue3";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
h,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import {
|
||||
CalendarDays,
|
||||
|
|
@ -67,8 +75,10 @@ function performFilter() {
|
|||
preserveState: true,
|
||||
preserveScroll: false,
|
||||
only,
|
||||
reset: isCompleted.value
|
||||
? ["completedJobs"]
|
||||
: ["pendingJobs", "processedJobs"],
|
||||
onSuccess: () => {
|
||||
resetLists();
|
||||
isFiltering.value = false;
|
||||
},
|
||||
onError: () => {
|
||||
|
|
@ -83,92 +93,6 @@ function clearFilters() {
|
|||
clientFilter.value = "all";
|
||||
}
|
||||
|
||||
// ── Infinite scroll lists ────────────────────────────────────────────────────
|
||||
const pendingList = ref(props.pendingJobs?.data ?? []);
|
||||
const processedList = ref(props.processedJobs?.data ?? []);
|
||||
const completedList = ref(props.completedJobs?.data ?? []);
|
||||
|
||||
const pendingPage = ref(props.pendingJobs?.current_page ?? 1);
|
||||
const processedPage = ref(props.processedJobs?.current_page ?? 1);
|
||||
const completedPage = ref(props.completedJobs?.current_page ?? 1);
|
||||
|
||||
const pendingLastPage = ref(props.pendingJobs?.last_page ?? 1);
|
||||
const processedLastPage = ref(props.processedJobs?.last_page ?? 1);
|
||||
const completedLastPage = ref(props.completedJobs?.last_page ?? 1);
|
||||
|
||||
const loadingPending = ref(false);
|
||||
const loadingProcessed = ref(false);
|
||||
const loadingCompleted = ref(false);
|
||||
|
||||
const pendingSentinel = ref(null);
|
||||
const processedSentinel = ref(null);
|
||||
const completedSentinel = ref(null);
|
||||
|
||||
function resetLists() {
|
||||
pendingList.value = props.pendingJobs?.data ?? [];
|
||||
processedList.value = props.processedJobs?.data ?? [];
|
||||
completedList.value = props.completedJobs?.data ?? [];
|
||||
pendingPage.value = props.pendingJobs?.current_page ?? 1;
|
||||
processedPage.value = props.processedJobs?.current_page ?? 1;
|
||||
completedPage.value = props.completedJobs?.current_page ?? 1;
|
||||
pendingLastPage.value = props.pendingJobs?.last_page ?? 1;
|
||||
processedLastPage.value = props.processedJobs?.last_page ?? 1;
|
||||
completedLastPage.value = props.completedJobs?.last_page ?? 1;
|
||||
clientFilter.value = props.filters.client || "all";
|
||||
}
|
||||
|
||||
function appendUnique(list, newItems) {
|
||||
const ids = new Set(list.value.map((i) => i.id));
|
||||
list.value.push(...newItems.filter((i) => !ids.has(i.id)));
|
||||
}
|
||||
|
||||
function buildPageUrl(pageParam, pageNum) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set(pageParam, pageNum);
|
||||
return `${window.location.pathname}?${params.toString()}`;
|
||||
}
|
||||
|
||||
function loadMore(listRef, pageRef, lastPageRef, loadingRef, propKey, pageParam) {
|
||||
if (loadingRef.value) return;
|
||||
if (pageRef.value >= lastPageRef.value) return;
|
||||
|
||||
const nextPage = pageRef.value + 1;
|
||||
loadingRef.value = true;
|
||||
|
||||
router.get(
|
||||
buildPageUrl(pageParam, nextPage),
|
||||
{},
|
||||
{
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
only: [propKey],
|
||||
onSuccess: () => {
|
||||
const newData = props[propKey]?.data ?? [];
|
||||
appendUnique(listRef, newData);
|
||||
pageRef.value = nextPage;
|
||||
lastPageRef.value = props[propKey]?.last_page ?? lastPageRef.value;
|
||||
loadingRef.value = false;
|
||||
},
|
||||
onError: () => {
|
||||
loadingRef.value = false;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function makeObserver(sentinelRef, loadFn) {
|
||||
const obs = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) loadFn();
|
||||
},
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
if (sentinelRef.value) obs.observe(sentinelRef.value);
|
||||
return obs;
|
||||
}
|
||||
|
||||
let observers = [];
|
||||
|
||||
// ── Scroll-hide title ────────────────────────────────────────────────────────
|
||||
const scrolled = ref(false);
|
||||
let stopNavigateListener = null;
|
||||
|
|
@ -189,43 +113,10 @@ onMounted(() => {
|
|||
}
|
||||
});
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
observers.push(
|
||||
makeObserver(pendingSentinel, () =>
|
||||
loadMore(
|
||||
pendingList,
|
||||
pendingPage,
|
||||
pendingLastPage,
|
||||
loadingPending,
|
||||
"pendingJobs",
|
||||
"pending"
|
||||
)
|
||||
),
|
||||
makeObserver(processedSentinel, () =>
|
||||
loadMore(
|
||||
processedList,
|
||||
processedPage,
|
||||
processedLastPage,
|
||||
loadingProcessed,
|
||||
"processedJobs",
|
||||
"processed"
|
||||
)
|
||||
),
|
||||
makeObserver(completedSentinel, () =>
|
||||
loadMore(
|
||||
completedList,
|
||||
completedPage,
|
||||
completedLastPage,
|
||||
loadingCompleted,
|
||||
"completedJobs",
|
||||
"completed"
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (stopNavigateListener) stopNavigateListener();
|
||||
observers.forEach((o) => o.disconnect());
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
});
|
||||
|
||||
|
|
@ -531,47 +422,91 @@ const JobCard = defineComponent({
|
|||
|
||||
<!-- Pending tab -->
|
||||
<TabsContent value="pending" class="space-y-3">
|
||||
<template v-if="pendingList.length">
|
||||
<JobCard
|
||||
v-for="job in pendingList"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-blue-500"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loadingPending"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<ClipboardList class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all" ? "Ni zadetkov" : "Ni novih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="pendingSentinel" class="h-px" />
|
||||
<div v-if="loadingPending" class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
<InfiniteScroll data="pendingJobs" only-next>
|
||||
<template #default="{ loading }">
|
||||
<template v-if="props.pendingJobs?.data?.length">
|
||||
<JobCard
|
||||
v-for="job in props.pendingJobs.data"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-blue-500"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loading"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<ClipboardList class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all" ? "Ni zadetkov" : "Ni novih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</TabsContent>
|
||||
|
||||
<!-- Processed tab -->
|
||||
<TabsContent value="processed" class="space-y-3">
|
||||
<template v-if="processedList.length">
|
||||
<InfiniteScroll data="processedJobs" only-next>
|
||||
<template #default="{ loading }">
|
||||
<template v-if="props.processedJobs?.data?.length">
|
||||
<JobCard
|
||||
v-for="job in props.processedJobs.data"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-green-500"
|
||||
:show-last-activity="true"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loading"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Ni obdelanih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<!-- Completed-today mode: single scroll list -->
|
||||
<div v-else class="px-4 pt-4 space-y-3">
|
||||
<InfiniteScroll data="completedJobs" only-next>
|
||||
<template #default="{ loading }">
|
||||
<template v-if="props.completedJobs?.data?.length">
|
||||
<JobCard
|
||||
v-for="job in processedList"
|
||||
v-for="job in props.completedJobs.data"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-green-500"
|
||||
accent-class="border-l-purple-500"
|
||||
:show-last-activity="true"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loadingProcessed"
|
||||
v-else-if="!loading"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
|
|
@ -579,49 +514,17 @@ const JobCard = defineComponent({
|
|||
{{
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Ni obdelanih opravil"
|
||||
: "Danes ni zaključenih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="processedSentinel" class="h-px" />
|
||||
<div v-if="loadingProcessed" class="space-y-3">
|
||||
</template>
|
||||
<template #loading>
|
||||
<div class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<!-- Completed-today mode: single scroll list -->
|
||||
<div v-else class="px-4 pt-4 space-y-3">
|
||||
<template v-if="completedList.length">
|
||||
<JobCard
|
||||
v-for="job in completedList"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
:href="jobHref(job)"
|
||||
accent-class="border-l-purple-500"
|
||||
:show-last-activity="true"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!loadingCompleted"
|
||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||
>
|
||||
<CheckCircle2 class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||
<p class="text-sm">
|
||||
{{
|
||||
search || clientFilter !== "all"
|
||||
? "Ni zadetkov"
|
||||
: "Danes ni zaključenih opravil"
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Sentinel for infinite scroll -->
|
||||
<div ref="completedSentinel" class="h-px" />
|
||||
<div v-if="loadingCompleted" class="space-y-3">
|
||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||
</div>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</div>
|
||||
</AppPhoneLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user