feat: add byMonth method to AttendanceController and corresponding service logic
This commit is contained in:
parent
86cc8637bd
commit
4758b19d2a
@ -52,4 +52,12 @@ public function byDate(Request $request): ?array
|
||||
|
||||
return $this->service->getByDate($date);
|
||||
}
|
||||
|
||||
public function byMonth(Request $request): array
|
||||
{
|
||||
$year = $request->integer('year', now()->year);
|
||||
$month = $request->integer('month', now()->month);
|
||||
|
||||
return $this->service->getByMonthData($year, $month);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ public function __construct(
|
||||
public function getIndexData(int $year, int $month): array
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = self::hasAnyRole([Role::DEVELOPER, Role::OWNER]);
|
||||
$isAdmin = self::hasAnyRole([Role::DEVELOPER, Role::OWNER, Role::ADMIN_TOKO, Role::DIREKTUR]);
|
||||
$hrSettings = app(HRSettings::class);
|
||||
|
||||
$employeeId = $isAdmin ? null : $user->employee?->id;
|
||||
@ -57,9 +57,9 @@ public function getByMonth(int $year, int $month, ?int $employeeId = null): Coll
|
||||
return Attendance::with(['employee.user.userProfile', 'media'])
|
||||
->whereYear('attendance_date', $year)
|
||||
->whereMonth('attendance_date', $month)
|
||||
->when($employeeId, fn ($q) => $q->where('employee_id', $employeeId))
|
||||
->when($employeeId, fn($q) => $q->where('employee_id', $employeeId))
|
||||
->get()
|
||||
->map(fn (Attendance $attendance) => $this->formatAttendance($attendance));
|
||||
->map(fn(Attendance $attendance) => $this->formatAttendance($attendance));
|
||||
}
|
||||
|
||||
public function getByDate(string $date): ?array
|
||||
@ -72,6 +72,18 @@ public function getByDate(string $date): ?array
|
||||
return $attendance ? $this->formatAttendance($attendance) : null;
|
||||
}
|
||||
|
||||
public function getByMonthData(int $year, int $month): array
|
||||
{
|
||||
$employeeId = null;
|
||||
|
||||
return [
|
||||
'attendances' => $this->getByMonth($year, $month, $employeeId),
|
||||
'leaves' => $this->getLeavesByMonth($year, $month, $employeeId),
|
||||
'employees' => $this->employeeService->getAll(),
|
||||
'monthStats' => $this->getMonthStats($year, $month, $employeeId),
|
||||
];
|
||||
}
|
||||
|
||||
public function getToday(): ?array
|
||||
{
|
||||
return $this->getByDate(now()->toDateString());
|
||||
@ -86,9 +98,9 @@ public function getLeavesByMonth(int $year, int $month, ?int $employeeId = null)
|
||||
->with('employee.user.userProfile')
|
||||
->where('start_date', '<=', $endOfMonth)
|
||||
->where('end_date', '>=', $startOfMonth)
|
||||
->when($employeeId, fn ($q) => $q->where('employee_id', $employeeId))
|
||||
->when($employeeId, fn($q) => $q->where('employee_id', $employeeId))
|
||||
->get()
|
||||
->map(fn (LeaveRequest $leave) => [
|
||||
->map(fn(LeaveRequest $leave) => [
|
||||
'id' => $leave->id,
|
||||
'employee_id' => $leave->employee_id,
|
||||
'start_date' => $leave->start_date->toDateString(),
|
||||
@ -174,7 +186,7 @@ public function checkIn(array $data): Attendance
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR],
|
||||
title: 'Presensi Masuk',
|
||||
body: 'Presensi masuk oleh '.auth()->user()->full_name.'.',
|
||||
body: 'Presensi masuk oleh ' . auth()->user()->full_name . '.',
|
||||
url: route('admin.hr.attendances.index', ['highlight' => $attendance->id]),
|
||||
);
|
||||
|
||||
@ -196,7 +208,7 @@ public function checkOut(Attendance $attendance, array $data): Attendance
|
||||
NotificationService::notify(
|
||||
roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR],
|
||||
title: 'Presensi Pulang',
|
||||
body: 'Presensi pulang oleh '.auth()->user()->full_name.'.',
|
||||
body: 'Presensi pulang oleh ' . auth()->user()->full_name . '.',
|
||||
url: route('admin.hr.attendances.index', ['highlight' => $attendance->id]),
|
||||
);
|
||||
|
||||
@ -211,8 +223,8 @@ private function getMonthStatsForEmployee(int $year, int $month, Carbon $startOf
|
||||
->where('employee_id', $employeeId);
|
||||
|
||||
$attendanceDates = $attendanceQuery->pluck('attendance_date')
|
||||
->map(fn ($d) => Carbon::parse($d)->toDateString())
|
||||
->filter(fn ($d) => ! in_array(Carbon::parse($d)->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY]))
|
||||
->map(fn($d) => Carbon::parse($d)->toDateString())
|
||||
->filter(fn($d) => ! in_array(Carbon::parse($d)->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY]))
|
||||
->unique()
|
||||
->values();
|
||||
$attendanceCount = $attendanceDates->count();
|
||||
@ -252,18 +264,31 @@ private function getMonthStatsForEmployee(int $year, int $month, Carbon $startOf
|
||||
|
||||
private function getMonthStatsForAll(int $year, int $month, Carbon $startOfMonth, Carbon $statEnd, int $workingDays): array
|
||||
{
|
||||
$totalEmployees = Employee::whereHas('user', fn ($q) => $q->where('is_active', true))->count();
|
||||
$totalEmployees = Employee::whereHas('user', function ($q) {
|
||||
$q->active()
|
||||
->whereHas('roles', fn($rq) => $rq->whereHas('permissions', fn($pq) => $pq->where('name', 'attendances.view')));
|
||||
})->count();
|
||||
|
||||
$presentCount = Attendance::whereYear('attendance_date', $year)
|
||||
->whereMonth('attendance_date', $month)
|
||||
->where('attendance_date', '<=', $statEnd->toDateString())
|
||||
->whereHas('employee', fn ($q) => $q->whereHas('user', fn ($uq) => $uq->where('is_active', true)))
|
||||
->whereHas('employee', function ($q) {
|
||||
$q->whereHas('user', function ($uq) {
|
||||
$uq->active()
|
||||
->whereHas('roles', fn($rq) => $rq->whereHas('permissions', fn($pq) => $pq->where('name', 'attendances.view')));
|
||||
});
|
||||
})
|
||||
->count();
|
||||
|
||||
$leaveRequests = LeaveRequest::approved()
|
||||
->where('start_date', '<=', $statEnd)
|
||||
->where('end_date', '>=', $startOfMonth)
|
||||
->whereHas('employee', fn ($q) => $q->whereHas('user', fn ($uq) => $uq->where('is_active', true)))
|
||||
->whereHas('employee', function ($q) {
|
||||
$q->whereHas('user', function ($uq) {
|
||||
$uq->active()
|
||||
->whereHas('roles', fn($rq) => $rq->whereHas('permissions', fn($pq) => $pq->where('name', 'attendances.view')));
|
||||
});
|
||||
})
|
||||
->get();
|
||||
|
||||
$leaveDays = 0;
|
||||
@ -307,11 +332,11 @@ private function formatAttendance(Attendance $attendance): array
|
||||
$checkOutMedia = $attendance->getFirstMedia('checkout');
|
||||
|
||||
$toArray['check_in_photo'] = $checkInMedia
|
||||
? Cache::remember("attendance_check_in_{$checkInMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkInMedia->getPath()))
|
||||
? Cache::remember("attendance_check_in_{$checkInMedia->id}", now()->addMinutes(55), fn() => $this->s3Service->getTemporaryUrl($checkInMedia->getPath()))
|
||||
: null;
|
||||
|
||||
$toArray['check_out_photo'] = $checkOutMedia
|
||||
? Cache::remember("attendance_check_out_{$checkOutMedia->id}", now()->addMinutes(55), fn () => $this->s3Service->getTemporaryUrl($checkOutMedia->getPath()))
|
||||
? Cache::remember("attendance_check_out_{$checkOutMedia->id}", now()->addMinutes(55), fn() => $this->s3Service->getTemporaryUrl($checkOutMedia->getPath()))
|
||||
: null;
|
||||
|
||||
return $toArray;
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
index as attendanceIndex,
|
||||
byMonth as attendanceByMonth,
|
||||
store,
|
||||
update,
|
||||
} from '@/routes/admin/hr/attendances';
|
||||
@ -24,7 +24,7 @@ import {
|
||||
LogIn,
|
||||
LogOut,
|
||||
} from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo, useState, useCallback } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type Attendance = {
|
||||
@ -147,9 +147,9 @@ function formatMinutes(minutes: number | null): string {
|
||||
}
|
||||
|
||||
export default function AttendanceIndex({
|
||||
attendances,
|
||||
leaves,
|
||||
employees = [],
|
||||
attendances: initialAttendances,
|
||||
leaves: initialLeaves,
|
||||
employees: initialEmployees = [],
|
||||
todayAttendance,
|
||||
currentYear,
|
||||
currentMonth,
|
||||
@ -174,10 +174,30 @@ export default function AttendanceIndex({
|
||||
const [detailAttendance, setDetailAttendance] = useState<Attendance | null>(
|
||||
null,
|
||||
);
|
||||
const [attendances, setAttendances] = useState<Attendance[]>(initialAttendances);
|
||||
const [leaves, setLeaves] = useState<Leave[]>(initialLeaves);
|
||||
const [employees, setEmployees] = useState<Employee[]>(initialEmployees);
|
||||
const [loadingMonth, setLoadingMonth] = useState(false);
|
||||
|
||||
const viewYear = viewDate.getFullYear();
|
||||
const viewMonth = viewDate.getMonth() + 1;
|
||||
|
||||
const fetchMonthData = useCallback(async (year: number, month: number) => {
|
||||
setLoadingMonth(true);
|
||||
try {
|
||||
const url = attendanceByMonth.url({ query: { year, month } });
|
||||
const res = await fetch(url);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setAttendances(data.attendances);
|
||||
setLeaves(data.leaves);
|
||||
setEmployees(data.employees);
|
||||
}
|
||||
} finally {
|
||||
setLoadingMonth(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const attendanceByDate = useMemo(() => {
|
||||
const map = new Map<string, Attendance[]>();
|
||||
attendances.forEach((att) => {
|
||||
@ -254,47 +274,20 @@ export default function AttendanceIndex({
|
||||
const handlePrevMonth = () => {
|
||||
const newDate = subMonths(viewDate, 1);
|
||||
setViewDate(newDate);
|
||||
|
||||
if (!isAdmin) {
|
||||
router.get(
|
||||
attendanceIndex.url(),
|
||||
{
|
||||
year: newDate.getFullYear(),
|
||||
month: newDate.getMonth() + 1,
|
||||
},
|
||||
{ preserveState: true, preserveScroll: true },
|
||||
);
|
||||
}
|
||||
fetchMonthData(newDate.getFullYear(), newDate.getMonth() + 1);
|
||||
};
|
||||
|
||||
const handleNextMonth = () => {
|
||||
const newDate = addMonths(viewDate, 1);
|
||||
setViewDate(newDate);
|
||||
|
||||
if (!isAdmin) {
|
||||
router.get(
|
||||
attendanceIndex.url(),
|
||||
{
|
||||
year: newDate.getFullYear(),
|
||||
month: newDate.getMonth() + 1,
|
||||
},
|
||||
{ preserveState: true, preserveScroll: true },
|
||||
);
|
||||
}
|
||||
fetchMonthData(newDate.getFullYear(), newDate.getMonth() + 1);
|
||||
};
|
||||
|
||||
const handleGoToToday = () => {
|
||||
const now = new Date();
|
||||
setViewDate(now);
|
||||
setSelectedDate(now);
|
||||
|
||||
if (!isAdmin) {
|
||||
router.get(
|
||||
attendanceIndex.url(),
|
||||
{ year: now.getFullYear(), month: now.getMonth() + 1 },
|
||||
{ preserveState: true, preserveScroll: true },
|
||||
);
|
||||
}
|
||||
fetchMonthData(now.getFullYear(), now.getMonth() + 1);
|
||||
};
|
||||
|
||||
const handleCameraCapture = (dataUrl: string) => {
|
||||
@ -374,17 +367,10 @@ export default function AttendanceIndex({
|
||||
Menampilkan presensi dari notifikasi.
|
||||
<button
|
||||
onClick={() => {
|
||||
router.get(
|
||||
attendanceIndex.url(),
|
||||
{
|
||||
year: currentYear,
|
||||
month: currentMonth,
|
||||
},
|
||||
{
|
||||
replace: true,
|
||||
preserveState: true,
|
||||
},
|
||||
);
|
||||
const now = new Date();
|
||||
setViewDate(now);
|
||||
setSelectedDate(now);
|
||||
fetchMonthData(currentYear, currentMonth);
|
||||
}}
|
||||
className="ml-1 text-primary underline underline-offset-4 hover:text-primary/80"
|
||||
>
|
||||
@ -495,8 +481,7 @@ export default function AttendanceIndex({
|
||||
|
||||
const todayMidnight = new Date();
|
||||
todayMidnight.setHours(0, 0, 0, 0);
|
||||
const [cYear, cMonth, cDay] = String(cell.date).split('-').map(Number);
|
||||
const cellDate = new Date(cYear, cMonth - 1, cDay);
|
||||
const cellDate = new Date(cell.date);
|
||||
cellDate.setHours(0, 0, 0, 0);
|
||||
|
||||
const isPastDate = cellDate < todayMidnight;
|
||||
@ -543,7 +528,7 @@ export default function AttendanceIndex({
|
||||
<div className="mt-1 flex flex-col gap-0.5 overflow-hidden">
|
||||
{isAdmin ? (
|
||||
<>
|
||||
{[...employees].sort((a, b) => a.name.localeCompare(b.name)).map((emp) => {
|
||||
{cell.isCurrentMonth && !isFutureDate && [...employees].sort((a, b) => a.name.localeCompare(b.name)).map((emp) => {
|
||||
const att = dayAttendances.find((a) => a.employee_id === emp.id);
|
||||
const leave = dayLeaves.find((l) => l.employee_id === emp.id);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user