diff --git a/frontend/src/components/requests/RequestStatisticsPanel.tsx b/frontend/src/components/requests/RequestStatisticsPanel.tsx
new file mode 100644
index 00000000..ab51d889
--- /dev/null
+++ b/frontend/src/components/requests/RequestStatisticsPanel.tsx
@@ -0,0 +1,181 @@
+"use client";
+
+import { SearchableSelectSingle } from "@/components/shared/SearchableSelect";
+import { apiClient } from "@/lib/api";
+import { displayUserName } from "@/lib/shared";
+import type { RequestEmployeeStatistics, User } from "@/types/api";
+import { ChevronDown } from "lucide-react";
+import { useEffect, useState } from "react";
+
+const statsPeriods = [
+ { value: "all", label: "За всё время" },
+ { value: "year", label: "За год" },
+ { value: "month", label: "За месяц" },
+] as const;
+
+type StatsPeriod = (typeof statsPeriods)[number]["value"];
+
+type RequestStatisticsPanelProps = {
+ canView: boolean;
+ employees: User[];
+};
+
+export function RequestStatisticsPanel({
+ canView,
+ employees,
+}: RequestStatisticsPanelProps) {
+ const [open, setOpen] = useState(false);
+ const [employeeId, setEmployeeId] = useState
(null);
+ const [period, setPeriod] = useState("all");
+ const [stats, setStats] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!canView || !open || !employeeId) {
+ return;
+ }
+
+ let cancelled = false;
+ setLoading(true);
+ setError(null);
+
+ apiClient.getEmployeeRequestStatistics(employeeId, period)
+ .then((response) => {
+ if (!cancelled) {
+ setStats(response);
+ }
+ })
+ .catch((err: unknown) => {
+ if (!cancelled) {
+ setError(err instanceof Error ? err.message : "Не удалось загрузить статистику.");
+ }
+ })
+ .finally(() => {
+ if (!cancelled) {
+ setLoading(false);
+ }
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [canView, employeeId, open, period]);
+
+ if (!canView) return null;
+
+ const employeeOptions = employees.map((employee) => ({
+ id: employee.id,
+ name: displayUserName(employee),
+ }));
+
+ const statsItems = stats ? [
+ { label: "Всего заявлений", value: stats.total_submitted_requests },
+ { label: "Больничных", value: stats.sick_leave_requests_count },
+ { label: "Отгулов", value: stats.day_off_requests_count },
+ { label: "Дней на больничном", value: stats.sick_leave_days },
+ { label: "Дней на отгулах", value: stats.day_off_days },
+ { label: "Оплачиваемый отпуск, дней", value: stats.paid_vacation_days },
+ { label: "За свой счёт, дней", value: stats.unpaid_vacation_days },
+ ] : [];
+
+ return (
+
+
+
+ {open ? (
+
+
+
{
+ setEmployeeId(id);
+ setStats(null);
+ setError(null);
+ }}
+ placeholder="Выберите сотрудника"
+ />
+
+
+ {statsPeriods.map((periodOption) => {
+ const isActive = period === periodOption.value;
+ return (
+
+ );
+ })}
+
+
+
+ {!employeeId ? (
+
+ Выберите сотрудника, чтобы посмотреть статистику.
+
+ ) : loading || (!stats && !error) ? (
+
+
+
Загрузка статистики...
+
+ ) : error ? (
+
+ {error}
+
+ ) : (
+
+
+ {period === "all"
+ ? "Период: за всё время"
+ : period === "year"
+ ? "Период: текущий календарный год"
+ : "Период: текущий календарный месяц"}
+
+
+ {statsItems.map((item) => (
+
+
{item.label}
+
{item.value}
+
+ ))}
+
+
+ )}
+
+ ) : null}
+
+ );
+}
diff --git a/frontend/src/lib/api/requests.ts b/frontend/src/lib/api/requests.ts
index 41cd4027..dae2f4f7 100644
--- a/frontend/src/lib/api/requests.ts
+++ b/frontend/src/lib/api/requests.ts
@@ -31,8 +31,10 @@ export function createRequestsApi(request: RequestFn, getToken: GetTokenFn) {
return request(`/api/v1/requests/${qs ? '?' + qs : ''}`);
},
getRequest: (requestId: number) => request(`/api/v1/requests/${requestId}/`),
- getRequestEmployeeStatistics: (requestId: number): Promise =>
- request(`/api/v1/requests/${requestId}/employee-statistics/`),
+ getRequestEmployeeStatistics: (requestId: number, period: 'all' | 'year' | 'month' = 'all'): Promise =>
+ request(`/api/v1/requests/${requestId}/employee-statistics/?period=${period}`),
+ getEmployeeRequestStatistics: (employeeId: number, period: 'all' | 'year' | 'month' = 'all'): Promise =>
+ request(`/api/v1/requests/statistics/?employee_id=${employeeId}&period=${period}`),
createRequest: (data: Record, saveAs?: 'draft' | 'submit') => formDataRequest('/api/v1/requests/', 'POST', data, saveAs),
updateRequest: (requestId: number, data: Record, saveAs?: 'draft' | 'submit') => formDataRequest(`/api/v1/requests/${requestId}/`, 'PATCH', data, saveAs),
deleteRequest: (requestId: number): Promise => request(`/api/v1/requests/${requestId}/`, { method: 'DELETE' }),
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index 26e506a0..5b8f409a 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -372,6 +372,7 @@ export interface RequestComment {
export interface RequestEmployeeStatistics {
employee_id: number;
employee_name: string;
+ period: "all" | "year" | "month";
total_submitted_requests: number;
sick_leave_requests_count: number;
day_off_requests_count: number;