Files
mkd/components/objects/DistrictsSummary.tsx
2026-02-04 00:17:04 +05:00

203 lines
9.4 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { District, Building, DomaApplication, Employee } from '../../types';
import { PerformanceCard } from './PerformanceCard';
import { EditDistrictModal } from './EditDistrictModal';
import { backendApi } from '../../services/apiClient';
import { Plus } from 'lucide-react';
interface Props {
aggregatedData: any;
onSelectDistrict: (d: District) => void;
canManage: boolean;
onAddDistrict: (payload: { name: string; managerName: string }) => void;
onDeleteDistrict: (d: District) => void;
onViewStaff: (d: District) => void;
onDistrictUpdated?: () => void;
role: string;
employees?: Employee[];
}
export const DistrictsSummary: React.FC<Props> = ({
aggregatedData,
onSelectDistrict,
canManage,
onAddDistrict,
onDeleteDistrict,
onViewStaff,
onDistrictUpdated,
role,
employees = [],
}) => {
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [editDistrict, setEditDistrict] = useState<District | null>(null);
const [name, setName] = useState('');
const [managerName, setManagerName] = useState('Не назначен');
const [availableEmployees, setAvailableEmployees] = useState<Employee[]>([]);
const [loadingEmployees, setLoadingEmployees] = useState(false);
// Загружаем список сотрудников при открытии формы
useEffect(() => {
if (isCreateOpen) {
fetchEmployees();
}
}, [isCreateOpen]);
const fetchEmployees = async () => {
try {
setLoadingEmployees(true);
const data = await backendApi.getEmployees();
setAvailableEmployees(Array.isArray(data) ? data : []);
} catch (error) {
console.error('Error fetching employees:', error);
setAvailableEmployees([]);
} finally {
setLoadingEmployees(false);
}
};
const UNASSIGNED_MANAGER = 'Не назначен';
// Фильтруем сотрудников: только мастера и начальники участка
const eligibleManagers = availableEmployees.filter(emp => {
if (emp.status !== 'active') return false;
const positionLower = emp.position.toLowerCase();
return positionLower.includes('мастер') || positionLower.includes('начальник участка');
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) return;
onAddDistrict({ name: name.trim(), managerName: managerName === '' ? UNASSIGNED_MANAGER : managerName.trim() });
setIsCreateOpen(false);
setName('');
setManagerName(UNASSIGNED_MANAGER);
};
return (
<div className="space-y-6 animate-fade-in">
<div className="flex justify-between items-end px-1">
<div className="flex flex-col">
<h2 className="text-xl font-bold text-slate-800">Обзор участков</h2>
<span className="text-[10px] text-slate-500 font-medium uppercase mt-1">
{role === 'MASTER' ? 'Моя зона ответственности' : 'Контроль по всей компании'}
</span>
</div>
{canManage && (
<button
onClick={() => setIsCreateOpen(true)}
className="bg-primary-600 text-white p-2 rounded-xl shadow-lg flex items-center gap-2 px-3 text-xs font-bold active:scale-95 transition-transform"
>
<Plus className="w-4 h-4" /> Участок
</button>
)}
</div>
{isCreateOpen && canManage && (
<form
onSubmit={handleSubmit}
className="bg-white border border-slate-200 rounded-2xl p-4 flex flex-col md:flex-row gap-3 items-stretch md:items-end shadow-sm"
>
<div className="flex-1">
<label className="block text-[10px] font-black uppercase text-slate-400 mb-1">
Название участка
</label>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Например: Участок №3 (Южный)"
className="w-full border border-slate-200 rounded-xl px-3 py-2 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
/>
</div>
<div className="flex-1">
<label className="block text-[10px] font-black uppercase text-slate-400 mb-1">
Начальник участка
</label>
{loadingEmployees ? (
<div className="w-full border border-slate-200 rounded-xl px-3 py-2 text-sm text-slate-400 bg-slate-50">
Загрузка сотрудников...
</div>
) : (
<select
value={managerName}
onChange={(e) => setManagerName(e.target.value)}
className="w-full border border-slate-200 rounded-xl px-3 py-2 text-sm focus:ring-2 focus:ring-primary-500 outline-none bg-white"
>
<option value={UNASSIGNED_MANAGER}>Не назначен</option>
{eligibleManagers.length === 0 ? (
<option value="" disabled>Нет доступных мастеров или начальников участка</option>
) : (
eligibleManagers.map(emp => (
<option key={emp.id} value={emp.name}>
{emp.name} - {emp.position}
</option>
))
)}
</select>
)}
</div>
<div className="flex gap-2">
<button
type="button"
onClick={() => {
setIsCreateOpen(false);
setName('');
setManagerName('Не назначен');
}}
className="px-4 py-2 rounded-xl border border-slate-200 text-xs font-bold text-slate-500 bg-white hover:bg-slate-50"
>
Отмена
</button>
<button
type="submit"
className="px-4 py-2 rounded-xl bg-primary-600 text-white text-xs font-bold hover:bg-primary-700 active:scale-95 transition-transform"
>
Сохранить
</button>
</div>
</form>
)}
<div className="grid grid-cols-1 gap-4">
{Object.values(aggregatedData).map((data: any) => {
const assignedToDistrict = employees.filter(emp => {
const ids = emp.assignedDistrictIds?.length ? emp.assignedDistrictIds : (emp.assignedDistrictId ? [emp.assignedDistrictId] : []);
return ids.includes(data.district.id);
});
const staffCount = assignedToDistrict.length;
const managerFromDb = data.district.managerName?.trim();
const managerDisplay = (managerFromDb && managerFromDb !== UNASSIGNED_MANAGER)
? managerFromDb
: (assignedToDistrict.length > 0 ? assignedToDistrict[0].name : UNASSIGNED_MANAGER);
return (
<PerformanceCard
key={data.district.id}
title={data.district.name}
subtitle={managerDisplay}
applications={data.applications}
onClick={() => onSelectDistrict(data.district)}
type="district"
buildingCount={data.buildings.length}
staffCount={staffCount}
onDelete={canManage ? () => onDeleteDistrict(data.district) : undefined}
onViewStaff={() => onViewStaff(data.district)}
onEdit={canManage ? () => setEditDistrict(data.district) : undefined}
/>
);
})}
</div>
{editDistrict && (
<EditDistrictModal
district={editDistrict}
onClose={() => setEditDistrict(null)}
onSaved={() => {
onDistrictUpdated?.();
}}
employees={employees}
/>
)}
</div>
);
};