feat: add pending feedback count to user notifications and sidebar

This commit is contained in:
Yoga Pangestu 2026-08-31 14:48:02 +07:00
parent 86ea75ec91
commit 941de83949
5 changed files with 44 additions and 14 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Services\Admin\FeedbackService;
use App\Services\NotificationService; use App\Services\NotificationService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Inertia\Inertia; use Inertia\Inertia;
@ -48,6 +49,9 @@ public function share(Request $request): array
'unreadNotificationsCount' => fn () => $request->user() 'unreadNotificationsCount' => fn () => $request->user()
? app(NotificationService::class)->unreadCount($request->user()) ? app(NotificationService::class)->unreadCount($request->user())
: 0, : 0,
'pendingFeedbackCount' => fn () => $request->user()
? app(FeedbackService::class)->pendingCount($request->user())
: 0,
'notifications' => Inertia::merge(fn () => $request->user() 'notifications' => Inertia::merge(fn () => $request->user()
? app(NotificationService::class)->paginated($request->user()) ? app(NotificationService::class)->paginated($request->user())
: null)->append('data', 'id'), : null)->append('data', 'id'),

View File

@ -2,6 +2,7 @@
namespace App\Services\Admin; namespace App\Services\Admin;
use App\Enums\FeedbackStatus;
use App\Models\Feedback; use App\Models\Feedback;
use App\Models\User; use App\Models\User;
use App\Support\TextAlignAttributeSanitizer; use App\Support\TextAlignAttributeSanitizer;
@ -51,6 +52,14 @@ public function paginated(User $user, int $perPage = 25, string $search = '', ?s
->paginate($perPage); ->paginate($perPage);
} }
public function pendingCount(User $user): int
{
return Feedback::query()
->where('user_id', $user->id)
->where('status', '!=', FeedbackStatus::Resolved)
->count();
}
public function create(User $user, array $data): Feedback public function create(User $user, array $data): Feedback
{ {
return Feedback::create([ return Feedback::create([

View File

@ -224,21 +224,27 @@ function buildNavMain({
]; ];
} }
const navSecondary: { title: string; url: string; icon: Icon }[] = [ function buildNavSecondary(
{ pendingFeedbackCount: number,
title: 'Kritik dan Saran', ): { title: string; url: string; icon: Icon; badge?: number }[] {
url: feedbackRoute.url(), return [
icon: IconMessageDots, {
}, title: 'Kritik dan Saran',
{ url: feedbackRoute.url(),
title: 'Bantuan', icon: IconMessageDots,
url: '#', badge: pendingFeedbackCount,
icon: IconHelp, },
}, {
]; title: 'Bantuan',
url: '#',
icon: IconHelp,
},
];
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { name, auth } = usePage<{ auth: Auth }>().props; const { name, auth, pendingFeedbackCount } = usePage<{ auth: Auth }>()
.props;
const roleNames = auth?.user?.roles?.map((role) => role.name) ?? []; const roleNames = auth?.user?.roles?.map((role) => role.name) ?? [];
const isStaff = roleNames.some((role) => STAFF_ROLES.includes(role)); const isStaff = roleNames.some((role) => STAFF_ROLES.includes(role));
const isMahasiswa = !isStaff && roleNames.includes('mahasiswa'); const isMahasiswa = !isStaff && roleNames.includes('mahasiswa');
@ -273,7 +279,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<NavMain items={navMain} /> <NavMain items={navMain} />
<NavSecondary items={navSecondary} className="mt-auto" /> <NavSecondary
items={buildNavSecondary(pendingFeedbackCount ?? 0)}
className="mt-auto"
/>
</SidebarContent> </SidebarContent>
</Sidebar> </Sidebar>
); );

View File

@ -8,6 +8,7 @@ import {
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
SidebarMenu, SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
@ -20,6 +21,7 @@ export function NavSecondary({
title: string; title: string;
url: string; url: string;
icon: Icon; icon: Icon;
badge?: number;
}[]; }[];
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) { } & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
return ( return (
@ -34,6 +36,11 @@ export function NavSecondary({
<span>{item.title}</span> <span>{item.title}</span>
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
{!!item.badge && (
<SidebarMenuBadge>
{item.badge}
</SidebarMenuBadge>
)}
</SidebarMenuItem> </SidebarMenuItem>
))} ))}
</SidebarMenu> </SidebarMenu>

View File

@ -14,6 +14,7 @@ declare module '@inertiajs/core' {
auth: Auth; auth: Auth;
sidebarOpen: boolean; sidebarOpen: boolean;
unreadNotificationsCount: number; unreadNotificationsCount: number;
pendingFeedbackCount: number;
[key: string]: unknown; [key: string]: unknown;
}; };
} }