+
{/* Заголовок карточки */}
@@ -728,27 +826,39 @@ export function BusinessDemo() {
{wholesaler.name.charAt(0)}
-
+
-
{wholesaler.name}
- {wholesaler.verifiedBadges.includes('verified') && (
- Проверен
+
+ {wholesaler.name}
+
+ {wholesaler.verifiedBadges.includes("verified") && (
+
+ Проверен
+
)}
- {wholesaler.verifiedBadges.includes('premium') && (
- Premium
+ {wholesaler.verifiedBadges.includes("premium") && (
+
+ Premium
+
)}
-
-
{wholesaler.fullName}
-
ИНН: {wholesaler.inn}
-
+
+
+ {wholesaler.fullName}
+
+
+ ИНН: {wholesaler.inn}
+
+
{/* Рейтинг и статистика */}
{wholesaler.rating}
- ({wholesaler.reviewsCount})
+
+ ({wholesaler.reviewsCount})
+
{wholesaler.completedOrders} заказов
@@ -769,15 +879,19 @@ export function BusinessDemo() {
Товаров
-
{wholesaler.productsCount.toLocaleString()}
+
+ {wholesaler.productsCount.toLocaleString()}
+
-
+
Ответ
-
{wholesaler.responseTime}
+
+ {wholesaler.responseTime}
+
@@ -786,7 +900,11 @@ export function BusinessDemo() {
Категории:
{wholesaler.categories.map((category, index) => (
-
+
{category}
))}
@@ -827,10 +945,16 @@ export function BusinessDemo() {
Смотреть товары
-
@@ -839,6 +963,9 @@ export function BusinessDemo() {
+
+ {/* Космически-галактические табели рабочего времени */}
+
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/src/components/admin/ui-kit/timesheet-demo.tsx b/src/components/admin/ui-kit/timesheet-demo.tsx
new file mode 100644
index 0000000..ad7a16b
--- /dev/null
+++ b/src/components/admin/ui-kit/timesheet-demo.tsx
@@ -0,0 +1,3751 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+
+interface CalendarDay {
+ day: number;
+ status: string;
+ hours: number;
+ overtime: number;
+ workType: string | null;
+ mood: string | null;
+ efficiency: number | null;
+ tasks: number;
+ breaks: number;
+}
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { Progress } from "@/components/ui/progress";
+
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Clock,
+ Star,
+ Award,
+ ChevronLeft,
+ ChevronRight,
+ Settings,
+ Download,
+ Filter,
+ MoreHorizontal,
+ MapPin,
+ CheckCircle,
+ XCircle,
+ Coffee,
+ Home,
+ Plane,
+ Heart,
+ Zap,
+ Moon,
+ Activity,
+ Eye,
+ Plus,
+ X,
+} from "lucide-react";
+
+export function TimesheetDemo() {
+ const [selectedVariant, setSelectedVariant] = useState<
+ | "galaxy"
+ | "cosmic"
+ | "custom"
+ | "compact"
+ | "interactive"
+ | "multi-employee"
+ >("galaxy");
+ const [selectedEmployee, setSelectedEmployee] = useState("employee1");
+ const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth());
+ const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
+ const [animatedStats, setAnimatedStats] = useState(false);
+ const [editableCalendarData, setEditableCalendarData] = useState<
+ CalendarDay[]
+ >([]);
+ const [calendarData, setCalendarData] = useState
([]);
+
+ // Данные сотрудников
+ const employees = [
+ {
+ id: "employee1",
+ name: "Алексей Космонавтов",
+ position: "Senior Frontend Developer",
+ avatar: "/placeholder-employee-1.jpg",
+ department: "Отдел разработки",
+ level: "Senior",
+ experience: "5 лет",
+ efficiency: 95,
+ totalHours: 176,
+ workDays: 22,
+ overtime: 8,
+ projects: 3,
+ },
+ {
+ id: "employee2",
+ name: "Мария Звездочетова",
+ position: "UX/UI Designer",
+ avatar: "/placeholder-employee-2.jpg",
+ department: "Дизайн-студия",
+ level: "Middle",
+ experience: "3 года",
+ efficiency: 88,
+ totalHours: 168,
+ workDays: 21,
+ overtime: 4,
+ projects: 5,
+ },
+ {
+ id: "employee3",
+ name: "Иван Галактический",
+ position: "DevOps Engineer",
+ avatar: "/placeholder-employee-3.jpg",
+ department: "Инфраструктура",
+ level: "Lead",
+ experience: "7 лет",
+ efficiency: 92,
+ totalHours: 184,
+ workDays: 23,
+ overtime: 12,
+ projects: 2,
+ },
+ ];
+
+ // Состояние для универсального табеля
+ const [employeesList, setEmployeesList] = useState(employees);
+ const [showAddForm, setShowAddForm] = useState(false);
+ const [newEmployee, setNewEmployee] = useState({
+ name: '',
+ position: '',
+ department: '',
+ level: 'Junior'
+ });
+
+ // Генерируем данные календаря для всех сотрудников
+ const generateEmployeeCalendarData = () => {
+ const daysInMonth = new Date(selectedYear, selectedMonth + 1, 0).getDate();
+ const employeeData: { [key: string]: CalendarDay[] } = {};
+
+ employeesList.forEach(employee => {
+ employeeData[employee.id] = Array.from({ length: daysInMonth }, (_, i) => {
+ const dayOfWeek = (new Date(selectedYear, selectedMonth, 1).getDay() === 0 ? 6 : new Date(selectedYear, selectedMonth, 1).getDay() - 1 + i) % 7;
+ const isWeekend = dayOfWeek >= 5;
+
+ return {
+ day: i + 1,
+ status: isWeekend
+ ? "weekend"
+ : Math.random() > 0.95
+ ? "sick"
+ : Math.random() > 0.9
+ ? "vacation"
+ : "work",
+ hours: isWeekend ? 0 : Math.floor(Math.random() * 3) + 7,
+ overtime: Math.random() > 0.8 ? Math.floor(Math.random() * 3) + 1 : 0,
+ workType: isWeekend ? null : ["office", "remote", "hybrid"][Math.floor(Math.random() * 3)],
+ mood: isWeekend ? null : ["excellent", "good", "normal", "tired"][Math.floor(Math.random() * 4)],
+ efficiency: isWeekend ? null : Math.floor(Math.random() * 30) + 70,
+ tasks: isWeekend ? 0 : Math.floor(Math.random() * 8) + 2,
+ breaks: isWeekend ? 0 : Math.floor(Math.random() * 3) + 1,
+ };
+ });
+ });
+
+ return employeeData;
+ };
+
+ const [allEmployeesData, setAllEmployeesData] = useState(generateEmployeeCalendarData());
+
+ // Добавление нового сотрудника
+ const handleAddEmployee = () => {
+ if (newEmployee.name && newEmployee.position) {
+ const newEmp = {
+ id: `employee${Date.now()}`,
+ name: newEmployee.name,
+ position: newEmployee.position,
+ department: newEmployee.department,
+ level: newEmployee.level,
+ avatar: `/placeholder-employee-${employeesList.length + 1}.jpg`,
+ experience: "Новый сотрудник",
+ efficiency: Math.floor(Math.random() * 20) + 80,
+ totalHours: 0,
+ workDays: 0,
+ overtime: 0,
+ projects: Math.floor(Math.random() * 5) + 1,
+ };
+ setEmployeesList([...employeesList, newEmp]);
+ setNewEmployee({ name: '', position: '', department: '', level: 'Junior' });
+ setShowAddForm(false);
+ }
+ };
+
+ // Удаление сотрудника
+ const handleRemoveEmployee = (employeeId: string) => {
+ setEmployeesList(employeesList.filter(emp => emp.id !== employeeId));
+ };
+
+ // Получение цвета для сотрудника
+ const getEmployeeColor = (index: number) => {
+ const colors = [
+ 'from-cyan-500 to-blue-500',
+ 'from-pink-500 to-purple-500',
+ 'from-emerald-500 to-teal-500',
+ 'from-orange-500 to-red-500',
+ 'from-yellow-500 to-amber-500',
+ 'from-indigo-500 to-purple-500',
+ 'from-green-500 to-lime-500',
+ 'from-rose-500 to-pink-500'
+ ];
+ return colors[index % colors.length];
+ };
+
+ // Получение статуса дня для конкретного сотрудника
+ const getDayStatus = (employeeId: string, dayIndex: number) => {
+ return allEmployeesData[employeeId]?.[dayIndex] || null;
+ };
+
+ // Подсчет работающих сотрудников в конкретный день
+ const getWorkingEmployeesCount = (dayIndex: number) => {
+ return employeesList.filter(emp => {
+ const dayData = getDayStatus(emp.id, dayIndex);
+ return dayData?.status === 'work';
+ }).length;
+ };
+
+ // Анимация статистики
+ useEffect(() => {
+ const timer = setTimeout(() => setAnimatedStats(true), 500);
+ return () => clearTimeout(timer);
+ }, []);
+
+ // Обновляем данные при изменении списка сотрудников или месяца
+ useEffect(() => {
+ setAllEmployeesData(generateEmployeeCalendarData());
+ }, [employeesList, selectedMonth, selectedYear]);
+
+ // Инициализация данных календаря для интерактивного режима
+ useEffect(() => {
+ if (editableCalendarData.length === 0 && calendarData.length > 0) {
+ setEditableCalendarData([...calendarData]);
+ }
+ }, [calendarData, editableCalendarData.length]);
+
+ // Подсчет статистики на основе редактируемых данных
+ const interactiveStats = React.useMemo(() => {
+ if (editableCalendarData.length === 0) {
+ return {
+ totalHours: 0,
+ workDays: 0,
+ vacation: 0,
+ sick: 0,
+ overtime: 0,
+ avgEfficiency: 0,
+ };
+ }
+
+ const workDays = editableCalendarData.filter(
+ (day) => day.status === "work"
+ ).length;
+ const totalHours = editableCalendarData.reduce(
+ (sum, day) => sum + day.hours,
+ 0
+ );
+ const vacation = editableCalendarData.filter(
+ (day) => day.status === "vacation"
+ ).length;
+ const sick = editableCalendarData.filter(
+ (day) => day.status === "sick"
+ ).length;
+ const overtime = editableCalendarData.reduce(
+ (sum, day) => sum + day.overtime,
+ 0
+ );
+ const avgEfficiency =
+ workDays > 0
+ ? Math.round(
+ editableCalendarData.reduce(
+ (sum, day) => sum + (day.efficiency || 0),
+ 0
+ ) / workDays
+ )
+ : 0;
+
+ return {
+ totalHours,
+ workDays,
+ vacation,
+ sick,
+ overtime,
+ avgEfficiency,
+ };
+ }, [editableCalendarData]);
+
+ // Функция для изменения статуса дня
+ const toggleDayStatus = (dayIndex: number) => {
+ const statuses = ["work", "weekend", "vacation", "sick", "absent"];
+ const currentDay = editableCalendarData[dayIndex];
+ if (!currentDay) return;
+
+ const currentStatusIndex = statuses.indexOf(currentDay.status);
+ const nextStatusIndex = (currentStatusIndex + 1) % statuses.length;
+ const newStatus = statuses[nextStatusIndex];
+
+ const updatedData = [...editableCalendarData];
+ updatedData[dayIndex] = {
+ ...currentDay,
+ status: newStatus,
+ hours: newStatus === "work" ? 8 : 0,
+ overtime: newStatus === "work" ? Math.floor(Math.random() * 3) : 0,
+ };
+
+ setEditableCalendarData(updatedData);
+ };
+
+ const currentEmployee =
+ employees.find((emp) => emp.id === selectedEmployee) || employees[0];
+
+ // Обновление данных при изменении месяца/года
+ useEffect(() => {
+ const generateData = () => {
+ const daysInMonth = new Date(
+ selectedYear,
+ selectedMonth + 1,
+ 0
+ ).getDate();
+ const firstDay = new Date(selectedYear, selectedMonth, 1).getDay();
+ const adjustedFirstDay = firstDay === 0 ? 6 : firstDay - 1;
+
+ const workTypes = ["office", "remote", "hybrid"];
+ const moods = ["excellent", "good", "normal", "tired"];
+
+ return Array.from({ length: daysInMonth }, (_, i) => {
+ const dayOfWeek = (adjustedFirstDay + i) % 7;
+ const isWeekend = dayOfWeek >= 5;
+
+ return {
+ day: i + 1,
+ status: isWeekend
+ ? "weekend"
+ : Math.random() > 0.95
+ ? "sick"
+ : Math.random() > 0.9
+ ? "vacation"
+ : "work",
+ hours: isWeekend ? 0 : Math.floor(Math.random() * 3) + 7,
+ overtime: Math.random() > 0.8 ? Math.floor(Math.random() * 3) + 1 : 0,
+ workType: isWeekend
+ ? null
+ : workTypes[Math.floor(Math.random() * workTypes.length)],
+ mood: isWeekend
+ ? null
+ : moods[Math.floor(Math.random() * moods.length)],
+ efficiency: isWeekend ? null : Math.floor(Math.random() * 30) + 70,
+ tasks: isWeekend ? 0 : Math.floor(Math.random() * 8) + 2,
+ breaks: isWeekend ? 0 : Math.floor(Math.random() * 3) + 1,
+ };
+ });
+ };
+
+ setCalendarData(generateData());
+ setAnimatedStats(false);
+ const timer = setTimeout(() => setAnimatedStats(true), 300);
+ return () => clearTimeout(timer);
+ }, [selectedMonth, selectedYear]);
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "work":
+ return "bg-gradient-to-r from-emerald-500 to-green-500";
+ case "weekend":
+ return "bg-gradient-to-r from-slate-500 to-gray-500";
+ case "vacation":
+ return "bg-gradient-to-r from-blue-500 to-cyan-500";
+ case "sick":
+ return "bg-gradient-to-r from-amber-500 to-orange-500";
+ case "absent":
+ return "bg-gradient-to-r from-red-500 to-rose-500";
+ default:
+ return "bg-gradient-to-r from-slate-500 to-gray-500";
+ }
+ };
+
+ const getWorkTypeIcon = (workType: string | null) => {
+ switch (workType) {
+ case "office":
+ return ;
+ case "remote":
+ return ;
+ case "hybrid":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const getMoodIcon = (mood: string | null) => {
+ switch (mood) {
+ case "excellent":
+ return ;
+ case "good":
+ return ;
+ case "normal":
+ return ;
+ case "tired":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const monthNames = [
+ "Январь",
+ "Февраль",
+ "Март",
+ "Апрель",
+ "Май",
+ "Июнь",
+ "Июль",
+ "Август",
+ "Сентябрь",
+ "Октябрь",
+ "Ноябрь",
+ "Декабрь",
+ ];
+
+ const dayNames = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"];
+
+ // Статистика
+ const stats = {
+ totalHours: calendarData.reduce((sum, day) => sum + day.hours, 0),
+ workDays: calendarData.filter((day) => day.status === "work").length,
+ weekends: calendarData.filter((day) => day.status === "weekend").length,
+ vacation: calendarData.filter((day) => day.status === "vacation").length,
+ sick: calendarData.filter((day) => day.status === "sick").length,
+ overtime: calendarData.reduce((sum, day) => sum + day.overtime, 0),
+ avgEfficiency: Math.round(
+ calendarData
+ .filter((day) => day.efficiency)
+ .reduce(
+ (sum, day, _, arr) => sum + (day.efficiency || 0) / arr.length,
+ 0
+ )
+ ),
+ totalTasks: calendarData.reduce((sum, day) => sum + day.tasks, 0),
+ };
+
+ const renderGalaxyVariant = () => (
+
+ {/* Космический фон с анимацией */}
+
+
+
+ {/* Плавающие частицы */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {currentEmployee.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+
+
+
+
+ {currentEmployee.name}
+
+
+ {currentEmployee.position}
+
+
+ {currentEmployee.department}
+ •
+
+ {currentEmployee.level}
+
+ •
+ {currentEmployee.experience}
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}ч
+
+
+ Отработано в {monthNames[selectedMonth].toLowerCase()}
+
+
+
+
+
+ {currentEmployee.efficiency}%
+
+
+
+
+
+
+ {/* Навигация по месяцам */}
+
+
+
+
+
+
+
{
+ if (selectedMonth === 0) {
+ setSelectedMonth(11);
+ setSelectedYear(selectedYear - 1);
+ } else {
+ setSelectedMonth(selectedMonth - 1);
+ }
+ }}
+ className="text-white hover:bg-white/10"
+ >
+
+
+
+
+ {monthNames[selectedMonth]} {selectedYear}
+
+
+
{
+ if (selectedMonth === 11) {
+ setSelectedMonth(0);
+ setSelectedYear(selectedYear + 1);
+ } else {
+ setSelectedMonth(selectedMonth + 1);
+ }
+ }}
+ className="text-white hover:bg-white/10"
+ >
+
+
+
+
+
+
+
+ Экспорт
+
+
+
+
+
+
+
+
+
+ {/* Статистические карты */}
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}
+
+
Часов
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.workDays : 0}
+
+
Рабочих дней
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.vacation : 0}
+
+
Отпуск
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.sick : 0}
+
+
Больничный
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.overtime : 0}
+
+
Переработка
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.avgEfficiency : 0}%
+
+
Эффективность
+
+
+
+
+
+
+ {/* Календарь */}
+
+ {/* Заголовки дней недели */}
+
+ {dayNames.map((day) => (
+
+ {day}
+
+ ))}
+
+
+ {/* Дни месяца */}
+
+ {/* Пустые ячейки для начала месяца */}
+ {Array.from({
+ length:
+ new Date(selectedYear, selectedMonth, 1).getDay() === 0
+ ? 6
+ : new Date(selectedYear, selectedMonth, 1).getDay() - 1,
+ }).map((_, index) => (
+
+ ))}
+
+ {/* Дни месяца */}
+ {calendarData.map((day, index) => (
+
+
+
+
+ {day.day}
+
+ {day.workType && (
+
+ {getWorkTypeIcon(day.workType)}
+
+ )}
+
+
+ {day.status === "work" && (
+
+
+
+ {day.hours}ч
+
+ {day.overtime > 0 && (
+
+ +{day.overtime}
+
+ )}
+
+
+
+ {getMoodIcon(day.mood)}
+ {day.efficiency && (
+
+ {day.efficiency}%
+
+ )}
+
+
+ )}
+
+ {day.status !== "work" && day.status !== "weekend" && (
+
+ )}
+
+
+ ))}
+
+
+
+ {/* Легенда */}
+
+
+
+ );
+
+ const renderCosmicVariant = () => (
+
+ {/* Космический фон с эффектом туманности */}
+
+
+
+
+ {/* Звездное поле */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {currentEmployee.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+ {/* Орбитальные элементы */}
+
+
+
+
+
+
+
+
+
+
+
+ {currentEmployee.name}
+
+
+ {currentEmployee.position}
+
+
+
+ {currentEmployee.department}
+
+
+ {currentEmployee.level}
+
+
+ {currentEmployee.experience} опыта
+
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}
+
+
+ часов в {monthNames[selectedMonth].toLowerCase()}
+
+
+
+
+
+
+ {currentEmployee.efficiency}%
+
+
+
+
+
+ {currentEmployee.projects}
+
+
+
+
+
+
+ {/* Панель управления */}
+
+
+
+
+
+
+
{
+ if (selectedMonth === 0) {
+ setSelectedMonth(11);
+ setSelectedYear(selectedYear - 1);
+ } else {
+ setSelectedMonth(selectedMonth - 1);
+ }
+ }}
+ className="text-white hover:bg-white/10 rounded-xl"
+ >
+
+
+
+
+ {monthNames[selectedMonth]} {selectedYear}
+
+
+
{
+ if (selectedMonth === 11) {
+ setSelectedMonth(0);
+ setSelectedYear(selectedYear + 1);
+ } else {
+ setSelectedMonth(selectedMonth + 1);
+ }
+ }}
+ className="text-white hover:bg-white/10 rounded-xl"
+ >
+
+
+
+
+
+
+
+ Экспорт
+
+
+
+ Фильтр
+
+
+
+
+
+
+
+
+
+ {/* Круговая статистика */}
+
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}
+
+
Часов
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.workDays : 0}
+
+
Рабочих
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.vacation : 0}
+
+
Отпуск
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.sick : 0}
+
+
Больничный
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.overtime : 0}
+
+
Переработка
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.avgEfficiency : 0}%
+
+
КПД
+
+
+
+
+
+ {/* Календарь в виде гексагональной сетки */}
+
+ {/* Заголовки дней недели */}
+
+
+ {dayNames.map((day) => (
+
+ {day}
+
+ ))}
+
+
+
+ {/* Календарная сетка */}
+
+
+ {/* Пустые ячейки для начала месяца */}
+ {Array.from({
+ length:
+ new Date(selectedYear, selectedMonth, 1).getDay() === 0
+ ? 6
+ : new Date(selectedYear, selectedMonth, 1).getDay() - 1,
+ }).map((_, index) => (
+
+ ))}
+
+ {/* Дни месяца */}
+ {calendarData.map((day, index) => (
+
+ {/* Эффект свечения */}
+
+
+
+
+
+ {day.day}
+
+ {day.workType && (
+
+ {getWorkTypeIcon(day.workType)}
+
+ )}
+
+
+ {day.status === "work" && (
+
+
+
+ {day.hours}ч
+
+ {day.overtime > 0 && (
+
+ +{day.overtime}
+
+ )}
+
+
+
+ {getMoodIcon(day.mood)}
+ {day.efficiency && (
+
+
+ {day.efficiency}%
+
+
+
+ )}
+
+
+ )}
+
+ {day.status !== "work" && day.status !== "weekend" && (
+
+ )}
+
+
+ ))}
+
+
+
+
+ {/* Расширенная легенда */}
+
+
+ Легенда статусов
+
+
+
+
+
+
+
Работа
+
+ Обычный рабочий день
+
+
+
+
+
+
+
+
+ Выходной
+
+
+ Суббота/Воскресенье
+
+
+
+
+
+
Отпуск
+
+ Оплачиваемый отпуск
+
+
+
+
+
+
+
+
+ Больничный
+
+
+ По болезни
+
+
+
+
+
+
+
+
Прогул
+
+ Неявка без причины
+
+
+
+
+
+
+ {/* SVG градиенты для круговых диаграмм */}
+
+
+ );
+
+ const renderCustomVariant = () => (
+
+ {/* Космический фон с плавающими частицами и звездным полем (из Галактического) */}
+
+
+
+ {/* Плавающие частицы */}
+
+
+
+
+
+
+
+ {/* Звездное поле */}
+
+ {Array.from({ length: 20 }).map((_, i) => (
+
+ ))}
+
+
+
+
+ {/* Заголовок сотрудника */}
+
+
+
+
+
+ {currentEmployee.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+
+
+ {currentEmployee.name}
+
+
+ {currentEmployee.position}
+
+
+ {currentEmployee.department}
+ •
+ {currentEmployee.level}
+ •
+ {currentEmployee.experience}
+
+
+
+ {/* Круговые диаграммы статистики (из Космического) */}
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}
+
+
+
+
Часов
+
+
+
+
+
+
+
+ {currentEmployee.efficiency}%
+
+
+
+
Эффективность
+
+
+
+
+
+ {/* Навигация и управление */}
+
+
+
+
+
+
+
{
+ if (selectedMonth === 0) {
+ setSelectedMonth(11);
+ setSelectedYear(selectedYear - 1);
+ } else {
+ setSelectedMonth(selectedMonth - 1);
+ }
+ }}
+ >
+
+
+
+
+
+ {monthNames[selectedMonth]} {selectedYear}
+
+
+
+
{
+ if (selectedMonth === 11) {
+ setSelectedMonth(0);
+ setSelectedYear(selectedYear + 1);
+ } else {
+ setSelectedMonth(selectedMonth + 1);
+ }
+ }}
+ >
+
+
+
+
+
+
+
+ Экспорт
+
+
+
+
+
+
+
+ {/* Статистика с круговыми диаграммами (из Космического) */}
+
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}
+
+
Часов
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.workDays : 0}
+
+
Рабочих
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.vacation : 0}
+
+
Отпуск
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.sick : 0}
+
+
+ Больничный
+
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.overtime : 0}
+
+
+ Переработка
+
+
+
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.avgEfficiency : 0}%
+
+
КПД
+
+
+
+
+
+ {/* Гексагональная календарная сетка */}
+
+ {/* Заголовки дней недели */}
+
+ {dayNames.map((day) => (
+
+ {day}
+
+ ))}
+
+
+ {/* Календарная сетка */}
+
+ {/* Пустые ячейки для начала месяца */}
+ {Array.from({
+ length:
+ new Date(selectedYear, selectedMonth, 1).getDay() === 0
+ ? 6
+ : new Date(selectedYear, selectedMonth, 1).getDay() - 1,
+ }).map((_, index) => (
+
+ ))}
+
+ {/* Дни месяца */}
+ {calendarData.map((day) => (
+
+ {/* Эффект свечения */}
+
+
+
+
+
+ {day.day}
+
+ {day.workType && (
+
+ {getWorkTypeIcon(day.workType)}
+
+ )}
+
+
+ {day.status === "work" && (
+
+
+
+ {day.hours}ч
+
+ {day.overtime > 0 && (
+
+ +{day.overtime}
+
+ )}
+
+
+
+ {getMoodIcon(day.mood)}
+ {day.efficiency && (
+
+
+ {day.efficiency}%
+
+
+
+ )}
+
+
+ )}
+
+ {day.status !== "work" && day.status !== "weekend" && (
+
+ )}
+
+
+ ))}
+
+
+
+
+ {/* SVG градиенты для круговых диаграмм */}
+
+
+ );
+
+ // Компактный вариант для 13-дюймовых экранов
+ const renderCompactVariant = () => (
+
+ {/* Космический фон с плавающими частицами и звездным полем (из Галактического) */}
+
+
+
+ {/* Плавающие частицы */}
+
+
+
+
+
+ {/* Звездное поле */}
+
+ {Array.from({ length: 15 }).map((_, i) => (
+
+ ))}
+
+
+
+
+ {/* Компактный заголовок сотрудника */}
+
+
+
+
+
+
+ {currentEmployee.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+
+
+ {currentEmployee.name}
+
+
+ {currentEmployee.position}
+
+
+ {currentEmployee.department}
+ •
+ {currentEmployee.level}
+
+
+
+
+ {/* Компактная навигация */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Компактная статистика в одну строку */}
+
+
+
+
+
+
+ {animatedStats ? stats.totalHours : 0}
+
+
Часов
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.workDays : 0}
+
+
Рабочих
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.vacation : 0}
+
+
Отпуск
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.sick : 0}
+
+
Больничный
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.overtime : 0}
+
+
Переработка
+
+
+
+
+
+
+
+
+ {animatedStats ? stats.avgEfficiency : 0}%
+
+
КПД
+
+
+
+
+ {/* Компактная календарная сетка */}
+
+ {/* Заголовки дней недели */}
+
+
ПН
+
ВТ
+
СР
+
ЧТ
+
ПТ
+
СБ
+
ВС
+
+
+ {/* Календарная сетка */}
+
+ {calendarData.map((day, index) => (
+
+
+
+ {day.day}
+
+
+ {day.status === "work" && (
+
+ {day.hours}ч
+ {day.overtime > 0 && (
+ +{day.overtime}
+ )}
+
+ )}
+
+ {day.status !== "work" && day.status !== "weekend" && (
+
+ )}
+
+
+ ))}
+
+
+
+ {/* Компактная легенда */}
+
+
+
+ {/* SVG градиенты */}
+
+
+ );
+
+ // Интерактивный вариант с яркими цветами и кликабельными датами
+ const renderInteractiveVariant = () => (
+
+ {/* Космический фон с плавающими частицами и звездным полем */}
+
+
+
+ {/* Более яркие плавающие частицы */}
+
+
+
+
+
+ {/* Более яркое звездное поле */}
+
+ {Array.from({ length: 20 }).map((_, i) => (
+
+ ))}
+
+
+
+
+ {/* Компактный заголовок сотрудника с яркими цветами */}
+
+
+
+
+
+
+ {currentEmployee.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+
+
+ {currentEmployee.name}
+
+
+ {currentEmployee.position}
+
+
+ {currentEmployee.department}
+ •
+ {currentEmployee.level}
+
+
+
+
+ {/* Компактная навигация с яркими цветами */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Яркая статистика в одну строку */}
+
+
+
+
+
+
+ {animatedStats ? interactiveStats.totalHours : 0}
+
+
Часов
+
+
+
+
+
+
+
+
+ {animatedStats ? interactiveStats.workDays : 0}
+
+
Рабочих
+
+
+
+
+
+
+
+
+ {animatedStats ? interactiveStats.vacation : 0}
+
+
Отпуск
+
+
+
+
+
+
+
+
+ {animatedStats ? interactiveStats.sick : 0}
+
+
Больничный
+
+
+
+
+
+
+
+
+ {animatedStats ? interactiveStats.overtime : 0}
+
+
Переработка
+
+
+
+
+
+
+
+
+ {animatedStats ? interactiveStats.avgEfficiency : 0}%
+
+
КПД
+
+
+
+
+ {/* Интерактивная календарная сетка с яркими цветами */}
+
+ {/* Заголовки дней недели */}
+
+
ПН
+
ВТ
+
СР
+
ЧТ
+
ПТ
+
СБ
+
ВС
+
+
+ {/* Интерактивная календарная сетка */}
+
+ {editableCalendarData.map((day, index) => (
+
toggleDayStatus(index)}
+ className={`relative group cursor-pointer transition-all duration-300 transform hover:scale-105 ${
+ day.status === "work"
+ ? "bg-gradient-to-br from-emerald-400/30 to-green-400/30 border-emerald-400/50 hover:border-emerald-300/70 shadow-lg shadow-emerald-500/20"
+ : day.status === "weekend"
+ ? "bg-gradient-to-br from-slate-400/30 to-gray-400/30 border-slate-400/50 hover:border-slate-300/70 shadow-lg shadow-slate-500/20"
+ : day.status === "vacation"
+ ? "bg-gradient-to-br from-blue-400/30 to-cyan-400/30 border-blue-400/50 hover:border-blue-300/70 shadow-lg shadow-blue-500/20"
+ : day.status === "sick"
+ ? "bg-gradient-to-br from-amber-400/30 to-orange-400/30 border-amber-400/50 hover:border-amber-300/70 shadow-lg shadow-amber-500/20"
+ : "bg-gradient-to-br from-red-400/30 to-rose-400/30 border-red-400/50 hover:border-red-300/70 shadow-lg shadow-red-500/20"
+ } rounded-xl border backdrop-blur-sm p-2 h-16`}
+ >
+
+
+ {day.day}
+
+
+ {day.status === "work" && (
+
+ {day.hours}ч
+ {day.overtime > 0 && (
+ +{day.overtime}
+ )}
+
+ )}
+
+ {day.status !== "work" && day.status !== "weekend" && (
+
+ )}
+
+
+ {/* Индикатор интерактивности */}
+
+
+ ))}
+
+
+
+ {/* Яркая легенда с подсказкой */}
+
+
+
+ 💡 Кликните на дату, чтобы изменить статус
+
+
+
+
+ {/* Яркие SVG градиенты */}
+
+
+ );
+
+ // Интерактивный вариант для нескольких сотрудников с яркими цветами
+ const renderMultiEmployeeInteractiveVariant = () => {
+
+ const daysInMonth = new Date(selectedYear, selectedMonth + 1, 0).getDate();
+
+ return (
+
+ {/* Заголовок */}
+
+
+
+
+
+
+
+ Универсальный табель учета рабочего времени
+
+
+ {monthNames[selectedMonth]} {selectedYear} •{" "}
+ {employeesList.length} сотрудников
+
+
+
+
+
{
+ if (selectedMonth === 0) {
+ setSelectedMonth(11);
+ setSelectedYear(selectedYear - 1);
+ } else {
+ setSelectedMonth(selectedMonth - 1);
+ }
+ }}
+ className="text-white hover:bg-white/10 rounded-xl border border-cyan-400/30 hover:border-cyan-400/50"
+ >
+
+
+
+
+ {monthNames[selectedMonth]} {selectedYear}
+
+
+
{
+ if (selectedMonth === 11) {
+ setSelectedMonth(0);
+ setSelectedYear(selectedYear + 1);
+ } else {
+ setSelectedMonth(selectedMonth + 1);
+ }
+ }}
+ className="text-white hover:bg-white/10 rounded-xl border border-pink-400/30 hover:border-pink-400/50"
+ >
+
+
+
+
setShowAddForm(!showAddForm)}
+ className="text-white hover:bg-white/10 rounded-xl border border-green-400/30 hover:border-green-400/50"
+ >
+
+ Добавить сотрудника
+
+
+
+
+ Экспорт
+
+
+
+
+ {/* Форма добавления сотрудника */}
+ {showAddForm && (
+
+ )}
+
+
+
+ {/* Основной табель */}
+
+
+
+
+
+
+ {/* Заголовок таблицы */}
+
+
+
+ Сотрудник
+ |
+ {Array.from({ length: daysInMonth }, (_, i) => {
+ const date = new Date(selectedYear, selectedMonth, i + 1);
+ const dayOfWeek = date.getDay();
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
+ const workingCount = getWorkingEmployeesCount(i);
+
+ return (
+
+
+ {dayNames[dayOfWeek === 0 ? 6 : dayOfWeek - 1]}
+
+
+ {i + 1}
+
+ {workingCount > 0 && (
+
+ {workingCount} чел.
+
+ )}
+ |
+ );
+ })}
+
+ Итого
+ |
+
+
+
+ {/* Строки сотрудников */}
+
+ {employeesList.map((employee, employeeIndex) => {
+ const employeeData = allEmployeesData[employee.id] || [];
+ const totalHours = employeeData.reduce(
+ (sum, day) => sum + day.hours,
+ 0
+ );
+ const workDays = employeeData.filter(
+ (day) => day.status === "work"
+ ).length;
+ const colorGradient = getEmployeeColor(employeeIndex);
+
+ return (
+
+ {/* Информация о сотруднике */}
+
+
+
+
+
+
+ {employee.name
+ .split(" ")
+ .map((n) => n[0])
+ .join("")}
+
+
+
+
+ {employee.name}
+
+
+ {employee.position}
+
+
+ {employee.department}
+
+
+
+ handleRemoveEmployee(employee.id)}
+ className="text-red-400 hover:text-red-300 hover:bg-red-500/10 p-1 h-6 w-6"
+ >
+
+
+
+ |
+
+ {/* Дни месяца */}
+ {employeeData.map((day, dayIndex) => {
+ const date = new Date(
+ selectedYear,
+ selectedMonth,
+ day.day
+ );
+ const isWeekend =
+ date.getDay() === 0 || date.getDay() === 6;
+
+ return (
+
+
+ {day.status === "work" && (
+ <>
+
+ {day.hours}ч
+
+ {day.overtime > 0 && (
+
+ +{day.overtime}
+
+ )}
+ >
+ )}
+ {day.status === "weekend" && (
+ Вых
+ )}
+ {day.status === "vacation" && (
+ Отп
+ )}
+ {day.status === "sick" && (
+ Б/Л
+ )}
+ {day.status === "absent" && (
+ Пр
+ )}
+
+ |
+ );
+ })}
+
+ {/* Итого */}
+
+
+ {totalHours}ч
+
+
+ {workDays} дней
+
+ |
+
+ );
+ })}
+
+
+ {/* Итоговая строка */}
+
+
+
+ Итого по дням:
+ |
+ {Array.from({ length: daysInMonth }, (_, dayIndex) => {
+ const workingCount = getWorkingEmployeesCount(dayIndex);
+ const totalHours = employeesList.reduce((sum, emp) => {
+ const dayData = getDayStatus(emp.id, dayIndex);
+ return sum + (dayData?.hours || 0);
+ }, 0);
+
+ return (
+
+ {workingCount > 0 && (
+
+ {totalHours}ч
+
+ )}
+ {workingCount > 0 && (
+
+ {workingCount} чел
+
+ )}
+ |
+ );
+ })}
+
+
+ {employeesList.reduce((sum, emp) => {
+ const empData = allEmployeesData[emp.id] || [];
+ return (
+ sum +
+ empData.reduce(
+ (daySum, day) => daySum + day.hours,
+ 0
+ )
+ );
+ }, 0)}
+ ч
+
+ |
+
+
+
+
+
+
+
+ {/* Легенда */}
+
+
+
+
+
+ Легенда статусов
+
+
+
+
+ 8ч
+
+
+
Работа
+
Рабочий день
+
+
+
+
+
+ Вых
+
+
+
Выходной
+
Суббота/Воскресенье
+
+
+
+
+
+ Отп
+
+
+
Отпуск
+
Оплачиваемый отпуск
+
+
+
+
+
+ Б/Л
+
+
+
+ Больничный
+
+
По болезни
+
+
+
+
+
+
+
+
+ 💡 В заголовках дней показано количество работающих сотрудников
+
+
+ 📊 В итоговой строке показаны общие часы и количество
+ сотрудников по дням
+
+
+
+
+
+ );
+ };
+
+ return (
+
+ {/* Селектор вариантов */}
+
+
+
+
+ Табель учета рабочего времени
+
+
+
+
+
+
+
+
+ {/* Отображение выбранного варианта */}
+ {selectedVariant === "galaxy" && renderGalaxyVariant()}
+ {selectedVariant === "cosmic" && renderCosmicVariant()}
+ {selectedVariant === "custom" && renderCustomVariant()}
+ {selectedVariant === "compact" && renderCompactVariant()}
+ {selectedVariant === "interactive" && renderInteractiveVariant()}
+ {selectedVariant === "multi-employee" &&
+ renderMultiEmployeeInteractiveVariant()}
+
+ );
+}