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;
|
||||
@ -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());
|
||||
@ -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;
|
||||
|
||||
@ -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