Align Care header with other Ladill apps.
Deploy Ladill Care / deploy (push) Successful in 1m0s

Add Afia and notifications to the top bar, remove the duplicate desktop page
title, wire notification and AI routes, and fix mobile nav notification links.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-29 12:53:34 +00:00
co-authored by Cursor
parent 5411b10694
commit eaf6da5cdb
12 changed files with 378 additions and 15 deletions
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Care;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Models\Appointment;
use App\Models\Consultation;
use App\Models\Patient;
use App\Services\Afia\AfiaService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AiController extends Controller
{
use ScopesToAccount;
public function chat(Request $request, AfiaService $afia): JsonResponse
{
$validated = $request->validate([
'message' => ['required', 'string', 'max:2000'],
'history' => ['nullable', 'array', 'max:20'],
'history.*.role' => ['nullable', 'string'],
'history.*.text' => ['nullable', 'string'],
]);
if (! $afia->enabled()) {
return response()->json(['message' => 'Afia is not available right now.'], 503);
}
try {
$reply = $afia->chat(trim($validated['message']), $validated['history'] ?? [], $this->context($request));
} catch (\Throwable $e) {
report($e);
return response()->json(['message' => 'Afia could not respond right now. Please try again.'], 502);
}
return response()->json(['reply' => $reply]);
}
/** @return array<string, mixed> */
private function context(Request $request): array
{
$owner = $this->ownerRef($request);
$organization = $this->organization($request);
return [
'signed_in' => 'yes',
'patients_total' => Patient::owned($owner)->where('organization_id', $organization->id)->count(),
'appointments_today' => Appointment::owned($owner)
->where('organization_id', $organization->id)
->whereDate('scheduled_at', today())
->count(),
'queue_waiting' => Appointment::owned($owner)
->where('organization_id', $organization->id)
->where('status', Appointment::STATUS_WAITING)
->count(),
'in_consultation' => Appointment::owned($owner)
->where('organization_id', $organization->id)
->where('status', Appointment::STATUS_IN_CONSULTATION)
->count(),
'consultations_completed_today' => Consultation::owned($owner)
->whereHas('visit', fn ($q) => $q->where('organization_id', $organization->id))
->where('status', Consultation::STATUS_COMPLETED)
->whereDate('completed_at', today())
->count(),
];
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class NotificationController extends Controller
{
public function index(Request $request): View
{
$notifications = $request->user()
->notifications()
->latest()
->paginate(20);
return view('notifications.index', compact('notifications'));
}
public function unread(Request $request): JsonResponse
{
$notifications = $request->user()
->unreadNotifications()
->latest()
->take(10)
->get()
->map(fn ($n) => [
'id' => $n->id,
'type' => class_basename($n->type),
'title' => $n->data['title'] ?? 'Notification',
'message' => $n->data['message'] ?? '',
'icon' => $n->data['icon'] ?? 'bell',
'url' => $n->data['url'] ?? null,
'created_at' => $n->created_at->diffForHumans(),
]);
return response()->json([
'notifications' => $notifications,
'unread_count' => $request->user()->unreadNotifications()->count(),
]);
}
public function markAsRead(Request $request, string $id): JsonResponse
{
$notification = $request->user()
->notifications()
->where('id', $id)
->first();
if ($notification) {
$notification->markAsRead();
}
return response()->json(['success' => true]);
}
public function markAllAsRead(Request $request): JsonResponse
{
$request->user()->unreadNotifications->markAsRead();
return response()->json(['success' => true]);
}
}