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);
|
$que->whereDate('client_cases.created_at', '<=', $to);
|
||||||
})
|
})
|
||||||
->groupBy('client_cases.id')
|
->groupBy('client_cases.id')
|
||||||
->addSelect([
|
->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count')
|
||||||
\DB::raw('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')
|
||||||
\DB::raw('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'])
|
->with(['person.client', 'client.person'])
|
||||||
->orderByDesc('client_cases.created_at');
|
->orderByDesc('client_cases.created_at');
|
||||||
|
|
||||||
|
|
@ -915,8 +913,8 @@ public function show(ClientCase $clientCase)
|
||||||
->get(),
|
->get(),
|
||||||
'sms_senders' => \App\Models\SmsSender::query()
|
'sms_senders' => \App\Models\SmsSender::query()
|
||||||
->select(['id', 'profile_id'])
|
->select(['id', 'profile_id'])
|
||||||
->addSelect(\DB::raw('sname as name'))
|
->selectRaw('sname as name')
|
||||||
->addSelect(\DB::raw('phone_number as phone'))
|
->selectRaw('phone_number as phone')
|
||||||
->orderBy('sname')
|
->orderBy('sname')
|
||||||
->get(),
|
->get(),
|
||||||
'sms_templates' => \App\Models\SmsTemplate::query()
|
'sms_templates' => \App\Models\SmsTemplate::query()
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,8 @@ public function index(Client $client, Request $request)
|
||||||
})
|
})
|
||||||
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
||||||
->groupBy('clients.id')
|
->groupBy('clients.id')
|
||||||
->addSelect([
|
->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN client_cases.id END) as cases_with_active_contracts_count')
|
||||||
// Number of client cases for this client that have at least one active contract
|
->selectRaw('COALESCE(SUM(CASE WHEN contract_segment.id IS NOT NULL THEN accounts.balance_amount END), 0) as active_contracts_balance_sum')
|
||||||
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'),
|
|
||||||
])
|
|
||||||
->with('person')
|
->with('person')
|
||||||
->orderByDesc('clients.created_at');
|
->orderByDesc('clients.created_at');
|
||||||
|
|
||||||
|
|
@ -89,10 +85,8 @@ public function show(Client $client, Request $request)
|
||||||
})
|
})
|
||||||
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
||||||
->groupBy('client_cases.id')
|
->groupBy('client_cases.id')
|
||||||
->addSelect([
|
->selectRaw('COUNT(DISTINCT CASE WHEN contract_segment.id IS NOT NULL THEN contracts.id END) as active_contracts_count')
|
||||||
\DB::raw('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')
|
||||||
\DB::raw('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'])
|
->with(['person', 'client.person'])
|
||||||
->where('client_cases.active', 1)
|
->where('client_cases.active', 1)
|
||||||
->orderByDesc('client_cases.created_at')
|
->orderByDesc('client_cases.created_at')
|
||||||
|
|
|
||||||
|
|
@ -100,13 +100,13 @@ public function __invoke(SmsService $sms): Response
|
||||||
// Field jobs assigned today - cached
|
// Field jobs assigned today - cached
|
||||||
$fieldJobsAssignedToday = Cache::remember('dashboard:field_jobs_assigned_today:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) {
|
$fieldJobsAssignedToday = Cache::remember('dashboard:field_jobs_assigned_today:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) {
|
||||||
return FieldJob::query()
|
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'])
|
->select(['id', 'assigned_user_id', 'priority', 'assigned_at', 'created_at', 'contract_id'])
|
||||||
->with(['contract' => function ($q) {
|
->with(['contract' => function ($q) {
|
||||||
$q->select('id', 'uuid', 'reference', 'client_case_id')
|
$q->select('id', 'uuid', 'reference', 'client_case_id')
|
||||||
->with(['clientCase:id,uuid,person_id', 'clientCase.person:id,full_name', 'segments:id,name']);
|
->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)
|
->limit(15)
|
||||||
->get()
|
->get()
|
||||||
->map(function ($fj) {
|
->map(function ($fj) {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ public function index(Request $request)
|
||||||
'current_page' => $paginator->currentPage(),
|
'current_page' => $paginator->currentPage(),
|
||||||
'from' => $paginator->firstItem(),
|
'from' => $paginator->firstItem(),
|
||||||
'last_page' => $paginator->lastPage(),
|
'last_page' => $paginator->lastPage(),
|
||||||
|
'links' => $paginator->linkCollection()->toArray(),
|
||||||
'path' => $paginator->path(),
|
'path' => $paginator->path(),
|
||||||
'per_page' => $paginator->perPage(),
|
'per_page' => $paginator->perPage(),
|
||||||
'to' => $paginator->lastItem(),
|
'to' => $paginator->lastItem(),
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ public function update(Person $person, Request $request)
|
||||||
'tax_number' => 'nullable|integer',
|
'tax_number' => 'nullable|integer',
|
||||||
'social_security_number' => 'nullable|integer',
|
'social_security_number' => 'nullable|integer',
|
||||||
'description' => 'nullable|string|max:500',
|
'description' => 'nullable|string|max:500',
|
||||||
|
'employer' => 'nullable|string|max:255',
|
||||||
|
'birthday' => 'nullable|date',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$person->update($attributes);
|
$person->update($attributes);
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,6 @@ public function index(Request $request): \Inertia\Response
|
||||||
$search = $request->input('search');
|
$search = $request->input('search');
|
||||||
$clientFilter = $request->input('client');
|
$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 = [
|
$eagerLoad = [
|
||||||
'contract' => function ($q) {
|
'contract' => function ($q) {
|
||||||
$q->with([
|
$q->with([
|
||||||
|
|
@ -85,8 +80,8 @@ public function index(Request $request): \Inertia\Response
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
return Inertia::render('Phone/Index', [
|
return Inertia::render('Phone/Index', [
|
||||||
'pendingJobs' => $pendingQuery->paginate(15, pageName: 'pending'),
|
'pendingJobs' => Inertia::scroll(fn () => $pendingQuery->paginate(15, pageName: 'pending')),
|
||||||
'processedJobs' => $processedQuery->paginate(15, pageName: 'processed'),
|
'processedJobs' => Inertia::scroll(fn () => $processedQuery->paginate(15, pageName: 'processed')),
|
||||||
'clients' => $clients,
|
'clients' => $clients,
|
||||||
'view_mode' => 'assigned',
|
'view_mode' => 'assigned',
|
||||||
'filters' => [
|
'filters' => [
|
||||||
|
|
@ -102,11 +97,6 @@ public function completedToday(Request $request): \Inertia\Response
|
||||||
$search = $request->input('search');
|
$search = $request->input('search');
|
||||||
$clientFilter = $request->input('client');
|
$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();
|
$start = now()->startOfDay();
|
||||||
$end = now()->endOfDay();
|
$end = now()->endOfDay();
|
||||||
|
|
||||||
|
|
@ -166,7 +156,7 @@ public function completedToday(Request $request): \Inertia\Response
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
return Inertia::render('Phone/Index', [
|
return Inertia::render('Phone/Index', [
|
||||||
'completedJobs' => $query->paginate(15, pageName: 'completed'),
|
'completedJobs' => Inertia::scroll(fn () => $query->paginate(15, pageName: 'completed')),
|
||||||
'clients' => $clients,
|
'clients' => $clients,
|
||||||
'view_mode' => 'completed-today',
|
'view_mode' => 'completed-today',
|
||||||
'filters' => [
|
'filters' => [
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,21 @@
|
||||||
"barryvdh/laravel-dompdf": "^3.1",
|
"barryvdh/laravel-dompdf": "^3.1",
|
||||||
"diglactic/laravel-breadcrumbs": "^10.0",
|
"diglactic/laravel-breadcrumbs": "^10.0",
|
||||||
"http-interop/http-factory-guzzle": "^1.2",
|
"http-interop/http-factory-guzzle": "^1.2",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^3.0",
|
||||||
"laravel/framework": "12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/jetstream": "^5.2",
|
"laravel/jetstream": "^5.2",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/scout": "^10.11",
|
"laravel/scout": "^10.11",
|
||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.9",
|
||||||
"maatwebsite/excel": "^3.1",
|
"maatwebsite/excel": "^3.1",
|
||||||
"meilisearch/meilisearch-php": "^1.11",
|
"meilisearch/meilisearch-php": "^1.11",
|
||||||
"robertboes/inertia-breadcrumbs": "dev-laravel-12",
|
"robertboes/inertia-breadcrumbs": "^1.0",
|
||||||
"tightenco/ziggy": "^2.0",
|
"tightenco/ziggy": "^2.0",
|
||||||
"tijsverkoyen/css-to-inline-styles": "^2.2"
|
"tijsverkoyen/css-to-inline-styles": "^2.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
"laravel/boost": "^1.1",
|
"laravel/boost": "^2.2",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pint": "^1.13",
|
||||||
"laravel/sail": "^1.26",
|
"laravel/sail": "^1.26",
|
||||||
"mockery/mockery": "^1.6",
|
"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"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "2.0",
|
"@inertiajs/vue3": "^3.0",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
|
|
@ -952,26 +952,35 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@inertiajs/core": {
|
"node_modules/@inertiajs/core": {
|
||||||
"version": "2.0.17",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-3.0.3.tgz",
|
||||||
"integrity": "sha512-tvYoqiouQSJrP7i7zVq61yyuEjlL96UU4nkkOWtOajXZlubGN4XrgRpnygpDk1KBO8V2yBab3oUZm+aZImwTHg==",
|
"integrity": "sha512-/4sW/cfNpvujjVOZlB5UNypLGNySs7X7V8IMLNSK8+3j1KsUYGS5wpLd9EqAu8wy8RiW7PPra2rPwB6Lx/ACow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.8.2",
|
"@jridgewell/trace-mapping": "^0.3.31",
|
||||||
"es-toolkit": "^1.34.1",
|
"es-toolkit": "^1.33.0",
|
||||||
"qs": "^6.9.0"
|
"laravel-precognition": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"axios": "^1.13.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"axios": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@inertiajs/vue3": {
|
"node_modules/@inertiajs/vue3": {
|
||||||
"version": "2.0.17",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-3.0.3.tgz",
|
||||||
"integrity": "sha512-Al0IMHQSj5aTQBLUAkljFEMCw4YRwSiOSKzN8LAbvJpKwvJFgc/wSj3wVVpr/AO9y9mz1w2mtvjnDoOzsntPLw==",
|
"integrity": "sha512-bhJN+GS66g1tYH1p6flKkG1N8oaT5J7ZLqBkavN9mHC6bVfoQCUG6sCuA07WTDfo9tDaxU89wsSSAf4mhn3SuA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/core": "2.0.17",
|
"@inertiajs/core": "3.0.3",
|
||||||
"es-toolkit": "^1.33.0"
|
"es-toolkit": "^1.33.0",
|
||||||
|
"laravel-precognition": "^2.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
|
|
@ -3804,9 +3813,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-toolkit": {
|
"node_modules/es-toolkit": {
|
||||||
"version": "1.43.0",
|
"version": "1.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
|
||||||
"integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==",
|
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|
@ -4372,6 +4381,24 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/laravel-vite-plugin": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz",
|
||||||
|
|
@ -4875,19 +4902,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/object-is": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
|
||||||
|
|
@ -5098,22 +5112,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/quickselect": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
|
||||||
|
|
@ -5361,82 +5359,6 @@
|
||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/skema": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/skema/-/skema-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/skema/-/skema-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "2.0",
|
"@inertiajs/vue3": "^3.0",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Card, CardContent } from "@/Components/ui/card";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
label: String,
|
label: String,
|
||||||
value: [String, Number],
|
value: [String, Number],
|
||||||
icon: Object,
|
icon: [Object, Function],
|
||||||
iconBg: {
|
iconBg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "bg-primary/10",
|
default: "bg-primary/10",
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ function formatDateTimeNoSeconds(value) {
|
||||||
last_page: imports?.meta?.last_page,
|
last_page: imports?.meta?.last_page,
|
||||||
from: imports?.meta?.from,
|
from: imports?.meta?.from,
|
||||||
to: imports?.meta?.to,
|
to: imports?.meta?.to,
|
||||||
links: imports?.links,
|
links: imports?.meta?.links,
|
||||||
}"
|
}"
|
||||||
route-name="imports.index"
|
route-name="imports.index"
|
||||||
:only-props="['imports']"
|
:only-props="['imports']"
|
||||||
|
|
|
||||||
|
|
@ -163,9 +163,7 @@ const props = defineProps({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppLayout title="Uvozne predloge">
|
<AppLayout title="Uvozne predloge">
|
||||||
<template #header>
|
<template #header> </template>
|
||||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Uvozne predloge</h2>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="py-6">
|
<div class="py-6">
|
||||||
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,6 @@ import {
|
||||||
Download,
|
Download,
|
||||||
Eye,
|
Eye,
|
||||||
Building2,
|
Building2,
|
||||||
Phone,
|
|
||||||
Mail,
|
|
||||||
MapPin,
|
|
||||||
Activity,
|
Activity,
|
||||||
} from "lucide-vue-next";
|
} from "lucide-vue-next";
|
||||||
|
|
||||||
|
|
@ -284,16 +281,11 @@ const clientSummary = computed(() => {
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="flex items-center justify-between gap-3">
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<Button
|
<Button variant="outline" size="sm" @click="router.visit(route('phone.index'))">
|
||||||
variant="ghost"
|
<ArrowLeft />
|
||||||
size="sm"
|
|
||||||
@click="router.visit(route('phone.index'))"
|
|
||||||
class="shrink-0"
|
|
||||||
>
|
|
||||||
<ArrowLeft class="w-4 h-4 mr-1" />
|
|
||||||
Nazaj
|
Nazaj
|
||||||
</Button>
|
</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 }}
|
{{ client_case?.person?.full_name }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -303,7 +295,7 @@ const clientSummary = computed(() => {
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
class="bg-emerald-100 text-emerald-700 hover:bg-emerald-100"
|
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
|
Zaključeno danes
|
||||||
</Badge>
|
</Badge>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -311,25 +303,25 @@ const clientSummary = computed(() => {
|
||||||
@click="confirmComplete = true"
|
@click="confirmComplete = true"
|
||||||
class="bg-green-600 hover:bg-green-700"
|
class="bg-green-600 hover:bg-green-700"
|
||||||
>
|
>
|
||||||
<CheckCircle2 class="w-4 h-4 mr-2" />
|
<CheckCircle2 class="w-4 h-4" />
|
||||||
Zaključi
|
Zaključi
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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">
|
<div class="mx-auto max-w-5xl px-2 sm:px-4 space-y-4">
|
||||||
<!-- Client details (account holder) -->
|
<!-- Client details (account holder) -->
|
||||||
<Card class="gap-3">
|
<Card class="p-0 py-3 gap-3">
|
||||||
<CardHeader>
|
<CardHeader class="px-3 py-2">
|
||||||
<CardTitle class="flex items-center gap-2 text-base">
|
<CardTitle class="flex items-center gap-2 text-base">
|
||||||
<Building2 class="w-5 h-5 text-gray-500" />
|
<Building2 class="w-5 h-5 text-gray-500" />
|
||||||
<span class="truncate">{{ clientSummary.name }}</span>
|
<span class="truncate">{{ clientSummary.name }}</span>
|
||||||
<Badge variant="secondary">Naročnik</Badge>
|
<Badge variant="secondary">Naročnik</Badge>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent class="px-3">
|
||||||
<Separator class="mb-4" />
|
<Separator class="mb-4" />
|
||||||
<PersonDetailPhone
|
<PersonDetailPhone
|
||||||
:types="types"
|
:types="types"
|
||||||
|
|
@ -340,8 +332,8 @@ const clientSummary = computed(() => {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Person (case person) -->
|
<!-- Person (case person) -->
|
||||||
<Card class="gap-3">
|
<Card class="p-0 py-3 gap-3">
|
||||||
<CardHeader class="px-3">
|
<CardHeader class="px-3 py-2">
|
||||||
<CardTitle class="flex items-center gap-2 text-base">
|
<CardTitle class="flex items-center gap-2 text-base">
|
||||||
<User class="w-5 h-5 text-gray-500" />
|
<User class="w-5 h-5 text-gray-500" />
|
||||||
<span class="truncate">{{ client_case.person.full_name }}</span>
|
<span class="truncate">{{ client_case.person.full_name }}</span>
|
||||||
|
|
@ -353,6 +345,13 @@ const clientSummary = computed(() => {
|
||||||
>
|
>
|
||||||
{{ client_case.person.description }}
|
{{ client_case.person.description }}
|
||||||
</CardDescription>
|
</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>
|
</CardHeader>
|
||||||
<CardContent class="px-3">
|
<CardContent class="px-3">
|
||||||
<Separator class="mb-4" />
|
<Separator class="mb-4" />
|
||||||
|
|
@ -365,21 +364,21 @@ const clientSummary = computed(() => {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Contracts assigned to me -->
|
<!-- Contracts assigned to me -->
|
||||||
<Card class="p-0 pt-3 gap-1">
|
<Card class="p-0 py-3 gap-1">
|
||||||
<CardHeader class="px-4">
|
<CardHeader class="px-3 py-2 pb-0">
|
||||||
<CardTitle class="flex items-center gap-2">
|
<CardTitle class="flex items-center gap-2">
|
||||||
<FileText class="w-5 h-5" />
|
<FileText class="w-5 h-5" />
|
||||||
Pogodbe
|
Pogodbe
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="p-2">
|
<CardContent class="p-2 space-y-1">
|
||||||
<Card
|
<Card
|
||||||
v-for="c in contracts"
|
v-for="c in contracts"
|
||||||
:key="c.uuid || c.id"
|
:key="c.uuid || c.id"
|
||||||
class="overflow-hidden p-0 gap-3"
|
class="overflow-hidden p-0 gap-2"
|
||||||
>
|
>
|
||||||
<!-- Contract header: reference + type badge -->
|
<!-- 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">
|
<div class="flex items-center flex-wrap">
|
||||||
<CardTitle class="text-base font-semibold">
|
<CardTitle class="text-base font-semibold">
|
||||||
{{ c.reference || "Šifra pogodbe ni določena" }}
|
{{ c.reference || "Šifra pogodbe ni določena" }}
|
||||||
|
|
@ -408,12 +407,20 @@ const clientSummary = computed(() => {
|
||||||
<!-- Collapsibles: description, meta, last object -->
|
<!-- Collapsibles: description, meta, last object -->
|
||||||
<CardContent
|
<CardContent
|
||||||
v-if="
|
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"
|
class="pt-0 px-0 space-y-0"
|
||||||
>
|
>
|
||||||
<!-- Description + Meta Accordion -->
|
<!-- Description + Meta + Latest Object Accordion -->
|
||||||
<template v-if="c.description || (c.meta && Object.keys(c.meta).length)">
|
<template
|
||||||
|
v-if="
|
||||||
|
c.description ||
|
||||||
|
(c.meta && Object.keys(c.meta).length) ||
|
||||||
|
c.latest_object
|
||||||
|
"
|
||||||
|
>
|
||||||
<Separator />
|
<Separator />
|
||||||
<Accordion type="multiple" class="w-full">
|
<Accordion type="multiple" class="w-full">
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
|
|
@ -479,43 +486,48 @@ const clientSummary = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
<AccordionItem
|
||||||
</template>
|
v-if="c.latest_object"
|
||||||
|
value="latest_object"
|
||||||
<!-- Last object -->
|
class="border-b-0"
|
||||||
<template v-if="c.last_object">
|
:class="
|
||||||
<Separator
|
c.description || (c.meta && Object.keys(c.meta).length)
|
||||||
class="mb-3"
|
? 'border-t'
|
||||||
: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"
|
|
||||||
>
|
>
|
||||||
{{ c.last_object.description }}
|
<AccordionTrigger
|
||||||
</p>
|
class="px-3 py-2 text-xs font-medium uppercase tracking-wide hover:no-underline"
|
||||||
</div>
|
>
|
||||||
|
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>
|
</template>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<!-- Action buttons: full-width row at bottom -->
|
<!-- 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
|
<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"
|
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)"
|
@click="openDrawerAddActivity(c)"
|
||||||
|
|
@ -542,27 +554,27 @@ const clientSummary = computed(() => {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Activities -->
|
<!-- Activities -->
|
||||||
<Card>
|
<Card class="p-0 py-2 gap-2">
|
||||||
<CardHeader>
|
<CardHeader class="px-3 py-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<CardTitle class="flex items-center gap-2">
|
<CardTitle class="flex items-center gap-2">
|
||||||
<Activity class="w-5 h-5" />
|
<Activity class="w-5 h-5" />
|
||||||
Aktivnosti
|
Aktivnosti
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Button size="sm" @click="openDrawerAddActivity()">
|
<Button size="sm" @click="openDrawerAddActivity()">
|
||||||
<Plus class="w-4 h-4 mr-1" />
|
<Plus class="w-4 h-4" />
|
||||||
Nova
|
Nova
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="space-y-3">
|
<CardContent class="space-y-1 px-2">
|
||||||
<Card
|
<Card
|
||||||
v-for="a in activities"
|
v-for="a in activities"
|
||||||
:key="a.id"
|
: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>
|
<CardHeader class="px-3 py-2">
|
||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between">
|
||||||
<CardTitle class="text-sm font-medium truncate">
|
<CardTitle class="text-sm font-medium truncate">
|
||||||
{{ activityActionLine(a) || "Aktivnost" }}
|
{{ activityActionLine(a) || "Aktivnost" }}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|
@ -583,7 +595,7 @@ const clientSummary = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</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">
|
<div class="flex flex-wrap gap-1.5">
|
||||||
<Badge v-if="a.contract" variant="secondary" class="text-[10px]">
|
<Badge v-if="a.contract" variant="secondary" class="text-[10px]">
|
||||||
<FileText class="w-3 h-3 mr-1" />
|
<FileText class="w-3 h-3 mr-1" />
|
||||||
|
|
@ -609,7 +621,10 @@ const clientSummary = computed(() => {
|
||||||
{{ a.status }}
|
{{ a.status }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</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 }}
|
{{ a.note }}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -624,8 +639,8 @@ const clientSummary = computed(() => {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Documents (case + assigned contracts) -->
|
<!-- Documents (case + assigned contracts) -->
|
||||||
<Card>
|
<Card class="p-0 py-2 gap-2">
|
||||||
<CardHeader>
|
<CardHeader class="px-3 py-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<CardTitle class="flex items-center gap-2">
|
<CardTitle class="flex items-center gap-2">
|
||||||
<FileText class="w-5 h-5" />
|
<FileText class="w-5 h-5" />
|
||||||
|
|
@ -649,7 +664,7 @@ const clientSummary = computed(() => {
|
||||||
{{ d.name || d.original_name }}
|
{{ d.name || d.original_name }}
|
||||||
</div>
|
</div>
|
||||||
<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
|
<Badge
|
||||||
v-if="d.contract_reference"
|
v-if="d.contract_reference"
|
||||||
|
|
@ -659,6 +674,11 @@ const clientSummary = computed(() => {
|
||||||
Pogodba: {{ d.contract_reference }}
|
Pogodba: {{ d.contract_reference }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge v-else variant="outline" class="text-[10px]"> Primer </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">
|
<span v-if="d.created_at" class="flex items-center gap-1">
|
||||||
<Calendar class="w-3 h-3" />
|
<Calendar class="w-3 h-3" />
|
||||||
{{ new Date(d.created_at).toLocaleDateString("sl-SI") }}
|
{{ new Date(d.created_at).toLocaleDateString("sl-SI") }}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,16 @@ import {
|
||||||
} from "@/Components/ui/select";
|
} from "@/Components/ui/select";
|
||||||
import { Skeleton } from "@/Components/ui/skeleton";
|
import { Skeleton } from "@/Components/ui/skeleton";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
|
||||||
import { router } from "@inertiajs/vue3";
|
import { InfiniteScroll, router } from "@inertiajs/vue3";
|
||||||
import { computed, defineComponent, h, onMounted, onUnmounted, ref, watch } from "vue";
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
h,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
import { useDebounceFn } from "@vueuse/core";
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
import {
|
import {
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
|
|
@ -67,8 +75,10 @@ function performFilter() {
|
||||||
preserveState: true,
|
preserveState: true,
|
||||||
preserveScroll: false,
|
preserveScroll: false,
|
||||||
only,
|
only,
|
||||||
|
reset: isCompleted.value
|
||||||
|
? ["completedJobs"]
|
||||||
|
: ["pendingJobs", "processedJobs"],
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
resetLists();
|
|
||||||
isFiltering.value = false;
|
isFiltering.value = false;
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
|
@ -83,92 +93,6 @@ function clearFilters() {
|
||||||
clientFilter.value = "all";
|
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 ────────────────────────────────────────────────────────
|
// ── Scroll-hide title ────────────────────────────────────────────────────────
|
||||||
const scrolled = ref(false);
|
const scrolled = ref(false);
|
||||||
let stopNavigateListener = null;
|
let stopNavigateListener = null;
|
||||||
|
|
@ -189,43 +113,10 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("scroll", onScroll, { passive: true });
|
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(() => {
|
onUnmounted(() => {
|
||||||
if (stopNavigateListener) stopNavigateListener();
|
if (stopNavigateListener) stopNavigateListener();
|
||||||
observers.forEach((o) => o.disconnect());
|
|
||||||
window.removeEventListener("scroll", onScroll);
|
window.removeEventListener("scroll", onScroll);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -531,47 +422,91 @@ const JobCard = defineComponent({
|
||||||
|
|
||||||
<!-- Pending tab -->
|
<!-- Pending tab -->
|
||||||
<TabsContent value="pending" class="space-y-3">
|
<TabsContent value="pending" class="space-y-3">
|
||||||
<template v-if="pendingList.length">
|
<InfiniteScroll data="pendingJobs" only-next>
|
||||||
<JobCard
|
<template #default="{ loading }">
|
||||||
v-for="job in pendingList"
|
<template v-if="props.pendingJobs?.data?.length">
|
||||||
:key="job.id"
|
<JobCard
|
||||||
:job="job"
|
v-for="job in props.pendingJobs.data"
|
||||||
:href="jobHref(job)"
|
:key="job.id"
|
||||||
accent-class="border-l-blue-500"
|
:job="job"
|
||||||
/>
|
:href="jobHref(job)"
|
||||||
</template>
|
accent-class="border-l-blue-500"
|
||||||
<div
|
/>
|
||||||
v-else-if="!loadingPending"
|
</template>
|
||||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
<div
|
||||||
>
|
v-else-if="!loading"
|
||||||
<ClipboardList class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
||||||
<p class="text-sm">
|
>
|
||||||
{{
|
<ClipboardList class="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto" />
|
||||||
search || clientFilter !== "all" ? "Ni zadetkov" : "Ni novih opravil"
|
<p class="text-sm">
|
||||||
}}
|
{{
|
||||||
</p>
|
search || clientFilter !== "all" ? "Ni zadetkov" : "Ni novih opravil"
|
||||||
</div>
|
}}
|
||||||
<!-- Sentinel for infinite scroll -->
|
</p>
|
||||||
<div ref="pendingSentinel" class="h-px" />
|
</div>
|
||||||
<div v-if="loadingPending" class="space-y-3">
|
</template>
|
||||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
<template #loading>
|
||||||
</div>
|
<div class="space-y-3">
|
||||||
|
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</InfiniteScroll>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<!-- Processed tab -->
|
<!-- Processed tab -->
|
||||||
<TabsContent value="processed" class="space-y-3">
|
<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
|
<JobCard
|
||||||
v-for="job in processedList"
|
v-for="job in props.completedJobs.data"
|
||||||
:key="job.id"
|
:key="job.id"
|
||||||
:job="job"
|
:job="job"
|
||||||
:href="jobHref(job)"
|
:href="jobHref(job)"
|
||||||
accent-class="border-l-green-500"
|
accent-class="border-l-purple-500"
|
||||||
:show-last-activity="true"
|
:show-last-activity="true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
v-else-if="!loadingProcessed"
|
v-else-if="!loading"
|
||||||
class="py-16 text-center text-gray-500 dark:text-gray-400 space-y-2"
|
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" />
|
<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"
|
search || clientFilter !== "all"
|
||||||
? "Ni zadetkov"
|
? "Ni zadetkov"
|
||||||
: "Ni obdelanih opravil"
|
: "Danes ni zaključenih opravil"
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Sentinel for infinite scroll -->
|
</template>
|
||||||
<div ref="processedSentinel" class="h-px" />
|
<template #loading>
|
||||||
<div v-if="loadingProcessed" class="space-y-3">
|
<div class="space-y-3">
|
||||||
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
<Skeleton v-for="i in 3" :key="i" class="h-36 rounded-xl" />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</template>
|
||||||
</Tabs>
|
</InfiniteScroll>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppPhoneLayout>
|
</AppPhoneLayout>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user