changes to UI mostly

This commit is contained in:
Simon Pocrnjič
2025-09-30 22:00:03 +02:00
parent 53917f2ca0
commit db99a57030
18 changed files with 2169 additions and 777 deletions
+101 -21
View File
@@ -20,15 +20,43 @@ class ClientCaseContoller extends Controller
*/
public function index(ClientCase $clientCase, Request $request)
{
$query = $clientCase::query()
->with(['person', 'client.person'])
->where('active', 1)
->when($request->input('search'), function ($que, $search) {
$que->whereHas('person', function ($q) use ($search) {
$q->where('full_name', 'ilike', '%'.$search.'%');
});
})
->addSelect([
// Count of active contracts (a contract is considered active if it has an active pivot in contract_segment)
'active_contracts_count' => \DB::query()
->from('contracts')
->selectRaw('COUNT(*)')
->whereColumn('contracts.client_case_id', 'client_cases.id')
->whereNull('contracts.deleted_at')
->whereExists(function ($q) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.active', true);
}),
// Sum of balances for accounts of active contracts
'active_contracts_balance_sum' => \DB::query()
->from('contracts')
->join('accounts', 'accounts.contract_id', '=', 'contracts.id')
->selectRaw('COALESCE(SUM(accounts.balance_amount), 0)')
->whereColumn('contracts.client_case_id', 'client_cases.id')
->whereNull('contracts.deleted_at')
->whereExists(function ($q) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.active', true);
}),
])
->orderByDesc('created_at');
return Inertia::render('Cases/Index', [
'client_cases' => $clientCase::with(['person'])
->when($request->input('search'), fn ($que, $search) => $que->whereHas(
'person',
fn ($q) => $q->where('full_name', 'ilike', '%'.$search.'%')
)
)
->where('active', 1)
->orderByDesc('created_at')
'client_cases' => $query
->paginate(15, ['*'], 'client-cases-page')
->withQueryString(),
'filters' => $request->only(['search']),
@@ -122,7 +150,9 @@ public function storeContract(ClientCase $clientCase, StoreContractRequest $requ
});
return to_route('clientCase.show', $clientCase);
// Preserve segment filter if present
$segment = request('segment');
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment]);
}
public function updateContract(ClientCase $clientCase, string $uuid, UpdateContractRequest $request)
@@ -163,7 +193,9 @@ public function updateContract(ClientCase $clientCase, string $uuid, UpdateContr
});
return to_route('clientCase.show', $clientCase);
// Preserve segment filter if present
$segment = request('segment');
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment]);
}
public function storeActivity(ClientCase $clientCase, Request $request)
@@ -219,6 +251,20 @@ public function storeActivity(ClientCase $clientCase, Request $request)
}
public function deleteActivity(ClientCase $clientCase, \App\Models\Activity $activity, Request $request)
{
// Ensure activity belongs to this case
if ($activity->client_case_id !== $clientCase->id) {
abort(404);
}
\DB::transaction(function () use ($activity) {
$activity->delete();
});
return back()->with('success', 'Activity deleted.');
}
public function deleteContract(ClientCase $clientCase, string $uuid, Request $request)
{
$contract = Contract::where('uuid', $uuid)->firstOrFail();
@@ -227,7 +273,9 @@ public function deleteContract(ClientCase $clientCase, string $uuid, Request $re
$contract->delete();
});
return to_route('clientCase.show', $clientCase);
// Preserve segment filter if present
$segment = request('segment');
return to_route('clientCase.show', ['client_case' => $clientCase, 'segment' => $segment]);
}
public function updateContractSegment(ClientCase $clientCase, string $uuid, Request $request)
@@ -923,11 +971,25 @@ public function show(ClientCase $clientCase)
'phone_types' => \App\Models\Person\PhoneType::all(),
];
// Optional segment filter from query string
$segmentId = request()->integer('segment');
// Prepare contracts and a reference map
$contracts = $case->contracts()
$contractsQuery = $case->contracts()
->with(['type', 'account', 'objects', 'segments:id,name'])
->orderByDesc('created_at')
->get();
->orderByDesc('created_at');
if (! empty($segmentId)) {
// Filter to contracts that are in the provided segment and active on pivot
$contractsQuery->whereExists(function ($q) use ($segmentId) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.segment_id', $segmentId)
->where('contract_segment.active', true);
});
}
$contracts = $contractsQuery->get();
$contractRefMap = [];
foreach ($contracts as $c) {
$contractRefMap[$c->id] = $c->reference;
@@ -937,11 +999,11 @@ public function show(ClientCase $clientCase)
$contractIds = $contracts->pluck('id');
$contractDocs = Document::query()
->where('documentable_type', Contract::class)
->whereIn('documentable_id', $contractIds)
->when($contractIds->isNotEmpty(), fn ($q) => $q->whereIn('documentable_id', $contractIds))
->orderByDesc('created_at')
->get()
->map(function ($d) use ($contractRefMap) {
$arr = $d->toArray();
$arr = method_exists($d, 'toArray') ? $d->toArray() : (array) $d;
$arr['contract_reference'] = $contractRefMap[$d->documentable_id] ?? null;
$arr['documentable_type'] = Contract::class;
$arr['contract_uuid'] = optional(Contract::withTrashed()->find($d->documentable_id))->uuid;
@@ -950,7 +1012,7 @@ public function show(ClientCase $clientCase)
});
$caseDocs = $case->documents()->orderByDesc('created_at')->get()->map(function ($d) use ($case) {
$arr = $d->toArray();
$arr = method_exists($d, 'toArray') ? $d->toArray() : (array) $d;
$arr['documentable_type'] = ClientCase::class;
$arr['client_case_uuid'] = $case->uuid;
@@ -961,15 +1023,32 @@ public function show(ClientCase $clientCase)
->sortByDesc('created_at')
->values();
// Resolve current segment for display when filtered
$currentSegment = null;
if (! empty($segmentId)) {
$currentSegment = \App\Models\Segment::query()->select('id', 'name')->find($segmentId);
}
return Inertia::render('Cases/Show', [
'client' => $case->client()->with('person', fn ($q) => $q->with(['addresses', 'phones', 'bankAccounts']))->firstOrFail(),
'client_case' => $case,
'contracts' => $contracts,
'activities' => tap(
$case->activities()
->with(['action', 'decision', 'contract:id,uuid,reference', 'user:id,name'])
->orderByDesc('created_at')
->paginate(20, ['*'], 'activities'),
(function () use ($case, $segmentId, $contractIds) {
$q = $case->activities()
->with(['action', 'decision', 'contract:id,uuid,reference', 'user:id,name'])
->orderByDesc('created_at');
if (! empty($segmentId)) {
// Only activities for filtered contracts or unlinked (contract_id null)
$q->where(function ($qq) use ($contractIds) {
$qq->whereNull('contract_id');
if ($contractIds->isNotEmpty()) {
$qq->orWhereIn('contract_id', $contractIds);
}
});
}
return $q->paginate(20, ['*'], 'activities')->withQueryString();
})(),
function ($p) {
$p->getCollection()->transform(function ($a) {
$a->setAttribute('user_name', optional($a->user)->name);
@@ -984,6 +1063,7 @@ function ($p) {
'types' => $types,
'segments' => $case->segments()->wherePivot('active', true)->get(['segments.id', 'segments.name']),
'all_segments' => \App\Models\Segment::query()->where('active', true)->get(['id', 'name']),
'current_segment' => $currentSegment,
]);
}
+65 -10
View File
@@ -11,16 +11,48 @@ class ClientController extends Controller
{
public function index(Client $client, Request $request)
{
$query = $client::query()
->with('person')
->when($request->input('search'), function ($que, $search) {
$que->whereHas('person', function ($q) use ($search) {
$q->where('full_name', 'ilike', '%'.$search.'%');
});
})
->where('active', 1)
->addSelect([
// Number of client cases for this client that have at least one active contract
'cases_with_active_contracts_count' => DB::query()
->from('client_cases')
->join('contracts', 'contracts.client_case_id', '=', 'client_cases.id')
->selectRaw('COUNT(DISTINCT client_cases.id)')
->whereColumn('client_cases.client_id', 'clients.id')
->whereNull('contracts.deleted_at')
->whereExists(function ($q) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.active', true);
}),
// Sum of account balances for active contracts that belong to this client's cases
'active_contracts_balance_sum' => DB::query()
->from('contracts')
->join('accounts', 'accounts.contract_id', '=', 'contracts.id')
->selectRaw('COALESCE(SUM(accounts.balance_amount), 0)')
->whereExists(function ($q) {
$q->from('client_cases')
->whereColumn('client_cases.id', 'contracts.client_case_id')
->whereColumn('client_cases.client_id', 'clients.id');
})
->whereNull('contracts.deleted_at')
->whereExists(function ($q) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.active', true);
}),
])
->orderByDesc('created_at');
return Inertia::render('Client/Index', [
'clients' => $client::query()
->with('person')
->when($request->input('search'), fn ($que, $search) => $que->whereHas(
'person',
fn ($q) => $q->where('full_name', 'ilike', '%'.$search.'%')
)
)
->where('active', 1)
->orderByDesc('created_at')
'clients' => $query
->paginate(15)
->withQueryString(),
'filters' => $request->only(['search']),
@@ -42,12 +74,35 @@ public function show(Client $client, Request $request)
return Inertia::render('Client/Show', [
'client' => $data,
'client_cases' => $data->clientCases()
->with('person')
->with(['person', 'client.person'])
->when($request->input('search'), fn ($que, $search) => $que->whereHas(
'person',
fn ($q) => $q->where('full_name', 'ilike', '%'.$search.'%')
)
)
->addSelect([
'active_contracts_count' => \DB::query()
->from('contracts')
->selectRaw('COUNT(*)')
->whereColumn('contracts.client_case_id', 'client_cases.id')
->whereNull('contracts.deleted_at')
->whereExists(function ($q) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.active', true);
}),
'active_contracts_balance_sum' => \DB::query()
->from('contracts')
->join('accounts', 'accounts.contract_id', '=', 'contracts.id')
->selectRaw('COALESCE(SUM(accounts.balance_amount), 0)')
->whereColumn('contracts.client_case_id', 'client_cases.id')
->whereNull('contracts.deleted_at')
->whereExists(function ($q) {
$q->from('contract_segment')
->whereColumn('contract_segment.contract_id', 'contracts.id')
->where('contract_segment.active', true);
}),
])
->where('active', 1)
->orderByDesc('created_at')
->paginate(15)
+8 -1
View File
@@ -24,7 +24,7 @@ public function index(Request $request)
])->filter()->unique()->values();
$contracts = Contract::query()
->with(['clientCase.person', 'type', 'account'])
->with(['clientCase.person', 'clientCase.client.person', 'type', 'account'])
->when($segmentIds->isNotEmpty(), function ($q) use ($segmentIds) {
$q->whereHas('segments', function ($sq) use ($segmentIds) {
// Relation already filters on active pivots
@@ -38,6 +38,13 @@ public function index(Request $request)
->limit(50)
->get();
// Mirror client onto the contract for simpler frontend access: c.client.person.full_name
$contracts->each(function (Contract $contract): void {
if ($contract->relationLoaded('clientCase') && $contract->clientCase) {
$contract->setRelation('client', $contract->clientCase->client);
}
});
// Build active assignment map keyed by contract uuid for quicker UI checks
$assignments = collect();
if ($contracts->isNotEmpty()) {
+85 -2
View File
@@ -2,14 +2,98 @@
namespace App\Http\Controllers;
use App\Models\Segment;
use App\Http\Requests\StoreSegmentRequest;
use App\Http\Requests\UpdateSegmentRequest;
use App\Models\Segment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
class SegmentController extends Controller
{
public function index()
{
// Fetch active segments with number of active contracts and total balance sum of those contracts
// A contract is considered in a segment when the pivot is active=true.
$segments = Segment::query()
->where('active', true)
->withCount(['contracts as contracts_count' => function ($q) {
// On some drivers, wherePivot can compile oddly inside withCount; target the pivot table directly
$q->where('contract_segment.active', '=', 1);
}])
->get(['id', 'name', 'description']);
// Compute total balance per segment for active contracts
$balances = DB::table('segments')
->join('contract_segment', 'contract_segment.segment_id', '=', 'segments.id')
->join('contracts', 'contracts.id', '=', 'contract_segment.contract_id')
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
->where('segments.active', '=', 1)
->where('contract_segment.active', '=', 1)
->groupBy('segments.id')
->pluck(DB::raw('COALESCE(SUM(accounts.balance_amount),0) as total_balance'), 'segments.id');
$segments = $segments->map(function ($seg) use ($balances) {
$seg->total_balance = (string) ($balances[$seg->id] ?? 0);
return $seg;
});
return Inertia::render('Segments/Index', [
'segments' => $segments,
]);
}
public function show(\App\Models\Segment $segment)
{
// Retrieve contracts that are active in this segment, eager-loading required relations
$search = request('search');
$contractsQuery = \App\Models\Contract::query()
->whereHas('segments', function ($q) use ($segment) {
$q->where('segments.id', $segment->id)
->where('contract_segment.active', '=', 1);
})
->with([
'clientCase.person',
'clientCase.client.person',
'type',
'account',
])
->latest('id');
if (!empty($search)) {
$contractsQuery->where(function ($qq) use ($search) {
$qq->where('contracts.reference', 'ilike', '%'.$search.'%')
->orWhereHas('clientCase.person', function ($p) use ($search) {
$p->where('full_name', 'ilike', '%'.$search.'%');
})
->orWhereHas('clientCase.client.person', function ($p) use ($search) {
$p->where('full_name', 'ilike', '%'.$search.'%');
});
});
}
$contracts = $contractsQuery
->paginate(15)
->withQueryString();
// Mirror client onto the contract to simplify frontend access (c.client.person.full_name)
$items = collect($contracts->items());
$items->each(function ($contract) {
if ($contract->relationLoaded('clientCase') && $contract->clientCase) {
$contract->setRelation('client', $contract->clientCase->client);
}
});
if (method_exists($contracts, 'setCollection')) {
$contracts->setCollection($items);
}
return Inertia::render('Segments/Show', [
'segment' => $segment->only(['id','name','description']),
'contracts' => $contracts,
]);
}
public function settings(Request $request)
{
return Inertia::render('Settings/Segments/Index', [
@@ -41,4 +125,3 @@ public function update(UpdateSegmentRequest $request, Segment $segment)
return to_route('settings.segments')->with('success', 'Segment updated');
}
}