Files
mkd/components/pr/ResidentReportView.tsx

411 lines
24 KiB
TypeScript
Raw Normal View History

2026-02-04 00:17:04 +05:00
import React from 'react';
import { ResidentReportContent } from '../../types';
import { Download, Printer } from 'lucide-react';
interface ResidentReportViewProps {
content: ResidentReportContent;
buildingAddress?: string;
period?: string;
}
export const ResidentReportView: React.FC<ResidentReportViewProps> = ({
content,
buildingAddress,
period
}) => {
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
};
const formatNumber = (num: number) => {
return new Intl.NumberFormat('ru-RU', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(num);
};
const handleDownloadCSV = () => {
// TODO: Реализовать экспорт в CSV
alert('Экспорт в CSV будет реализован');
};
const handlePrint = () => {
window.print();
};
return (
<div className="space-y-6 animate-fade-in print:space-y-4">
{/* Кнопки действий */}
<div className="flex justify-end gap-3 print:hidden">
<button
onClick={handleDownloadCSV}
className="bg-primary-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-primary-700 transition-colors"
>
<Download className="w-4 h-4" />
Скачать CSV
</button>
<button
onClick={handlePrint}
className="bg-slate-100 text-slate-700 px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-slate-200 transition-colors"
>
<Printer className="w-4 h-4" />
Печать
</button>
</div>
{/* Параметры */}
{content.parameters && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none">
<h3 className="text-lg font-bold text-slate-800 mb-4">Параметры:</h3>
<div className="space-y-3">
<div>
<p className="text-sm text-slate-600 mb-1">Период: {content.parameters.periodStart && content.parameters.periodEnd
? `${new Date(content.parameters.periodStart).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' })} - ${new Date(content.parameters.periodEnd).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' })}`
: period || 'Не указан'}
</p>
<p className="text-sm text-slate-600 mb-1">Начало периода:</p>
<p className="text-sm text-slate-600 mb-1">Конец периода:</p>
<p className="text-sm text-slate-600 mb-1">Дом: {content.parameters.building || buildingAddress || 'Не указан'}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 pt-2 border-t border-slate-200">
{content.parameters.residentialArea && (
<div>
<p className="text-sm text-slate-600 mb-1">Ж (кв.м)</p>
<p className="font-bold text-slate-800">{formatNumber(content.parameters.residentialArea)}</p>
</div>
)}
{content.parameters.nonResidentialArea && (
<div>
<p className="text-sm text-slate-600 mb-1">НЖ (кв.м) в т.ч.:</p>
<p className="font-bold text-slate-800">{formatNumber(content.parameters.nonResidentialArea)}</p>
</div>
)}
{content.parameters.parkingArea && (
<div>
<p className="text-sm text-slate-600 mb-1">Парковка</p>
<p className="font-bold text-slate-800">{formatNumber(content.parameters.parkingArea)}</p>
</div>
)}
{content.parameters.totalArea && (
<div>
<p className="text-sm text-slate-600 mb-1">Итого (кв.м)</p>
<p className="font-bold text-slate-800">{formatNumber(content.parameters.totalArea)}</p>
</div>
)}
</div>
{content.tariffs && (
<div className="grid grid-cols-2 gap-4 pt-2 border-t border-slate-200">
<div>
<p className="text-sm text-slate-600 mb-1">Тариф</p>
<p className="font-bold text-slate-800">{formatNumber(content.tariffs.tariff)}</p>
</div>
<div>
<p className="text-sm text-slate-600 mb-1">Резервный фонд</p>
<p className="font-bold text-slate-800">{formatNumber(content.tariffs.reserveFund)}</p>
</div>
</div>
)}
</div>
</div>
)}
{/* Тарифы */}
{content.tariffs && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-slate-600 mb-1">Тариф</p>
<p className="font-bold text-slate-800">{formatNumber(content.tariffs.tariff)}</p>
</div>
<div>
<p className="text-sm text-slate-600 mb-1">Резервный фонд</p>
<p className="font-bold text-slate-800">{formatNumber(content.tariffs.reserveFund)}</p>
</div>
</div>
</div>
)}
{/* Услуги */}
{content.services && content.services.length > 0 && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none overflow-x-auto">
<h3 className="text-lg font-bold text-slate-800 mb-4">Услуги</h3>
<table className="w-full border-collapse">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left py-2 px-3 text-sm font-bold text-slate-700">Услуга</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">Долг</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">Начислено</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">Оплачено</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">% от плана</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">Порядок</th>
</tr>
</thead>
<tbody>
{content.services
.sort((a, b) => a.order - b.order)
.map((service, index) => (
<tr
key={index}
className={`border-b border-slate-100 hover:bg-slate-50 ${
service.name === 'СОДЕРЖАНИЕ ВСЕГО' ? 'bg-emerald-50 font-bold' : ''
}`}
>
<td className={`py-2 px-3 text-sm ${service.name === 'СОДЕРЖАНИЕ ВСЕГО' ? 'font-bold text-slate-900' : 'text-slate-800'}`}>
{service.name}
</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(service.debt)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(service.accrued)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(service.paid)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatNumber(service.percentOfPlan)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-500">{formatNumber(service.order)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* Сальдо */}
{content.balance && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none overflow-x-auto">
<h3 className="text-lg font-bold text-slate-800 mb-4">Сальдо</h3>
<table className="w-full border-collapse">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left py-2 px-3 text-sm font-bold text-slate-700">Сальдо</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">Сумма</th>
</tr>
</thead>
<tbody>
<tr className="border-b border-slate-100">
<td className="py-2 px-3 text-sm text-slate-700">Исходя из начисленных</td>
<td className={`py-2 px-3 text-sm text-right font-bold ${
(content.balance.fromAccrued || 0) < 0 ? 'text-red-600' : 'text-slate-800'
}`}>
{formatCurrency(content.balance.fromAccrued || 0)}
</td>
</tr>
<tr className="border-b border-slate-100">
<td className="py-2 px-3 text-sm text-slate-700">Исходя из поступивших средств</td>
<td className={`py-2 px-3 text-sm text-right font-bold ${
(content.balance.fromReceived || 0) < 0 ? 'text-red-600' : 'text-slate-800'
}`}>
{formatCurrency(content.balance.fromReceived || 0)}
</td>
</tr>
{content.balance.reserveFundFromAccrued !== undefined && (
<tr className="border-b border-slate-100">
<td className="py-2 px-3 text-sm text-slate-700">Сальдо Резервного фонда на начало периода исходя из Начисленых средств</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">
{formatCurrency(content.balance.reserveFundFromAccrued)}
</td>
</tr>
)}
{content.balance.reserveFundFromReceived !== undefined && (
<tr>
<td className="py-2 px-3 text-sm text-slate-700">Сальдо Резервного фонда на начало периода исходя из Поступивших средств</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">
{formatCurrency(content.balance.reserveFundFromReceived)}
</td>
</tr>
)}
</tbody>
</table>
</div>
)}
{/* Статьи затрат */}
{content.expenseItems && content.expenseItems.length > 0 && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none overflow-x-auto">
<h3 className="text-lg font-bold text-slate-800 mb-4">Статьи затрат</h3>
<table className="w-full border-collapse">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left py-2 px-3 text-sm font-bold text-slate-700"> п/п</th>
<th className="text-left py-2 px-3 text-sm font-bold text-slate-700">Статья затрат</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">В месяц</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">Сумма</th>
<th className="text-right py-2 px-3 text-sm font-bold text-slate-700">руб/кв.м в месяц</th>
</tr>
</thead>
<tbody>
{content.expenseItems.map((item, index) => (
<React.Fragment key={index}>
<tr className="border-b border-slate-100">
<td className="py-2 px-3 text-sm text-slate-800 font-medium">{item.number}</td>
<td className="py-2 px-3 text-sm text-slate-800 font-bold">{item.name}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(item.perMonth)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700 font-bold">{formatCurrency(item.total)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatNumber(item.perSquareMeter)}</td>
</tr>
{item.children && item.children.map((child, childIndex) => (
<React.Fragment key={`${index}-${childIndex}`}>
<tr className="border-b border-slate-50 bg-slate-50/50">
<td className="py-2 px-3 text-sm text-slate-700 pl-8">{child.number}</td>
<td className="py-2 px-3 text-sm text-slate-700">{child.name}</td>
<td className="py-2 px-3 text-sm text-right text-slate-600">{formatCurrency(child.perMonth)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-600">{formatCurrency(child.total)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-600">{formatNumber(child.perSquareMeter)}</td>
</tr>
{child.children && child.children.map((grandchild, grandchildIndex) => (
<tr key={`${index}-${childIndex}-${grandchildIndex}`} className="border-b border-slate-50 bg-slate-50/30">
<td className="py-2 px-3 text-sm text-slate-600 pl-12">{grandchild.number}</td>
<td className="py-2 px-3 text-sm text-slate-600">{grandchild.name}</td>
<td className="py-2 px-3 text-sm text-right text-slate-500">{formatCurrency(grandchild.perMonth)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-500">{formatCurrency(grandchild.total)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-500">{formatNumber(grandchild.perSquareMeter)}</td>
</tr>
))}
</React.Fragment>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
)}
{/* Итоги */}
{content.totals && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none overflow-x-auto">
<h3 className="text-lg font-bold text-slate-800 mb-4">Итоги</h3>
<table className="w-full border-collapse">
<tbody>
<tr className="border-b border-slate-200">
<td className="py-2 px-3 text-sm font-bold text-slate-800">Итого</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.totalExpenses / (content.parameters?.totalArea ? (new Date(content.parameters.periodEnd).getTime() - new Date(content.parameters.periodStart).getTime()) / (1000 * 60 * 60 * 24 * 30) : 1))}</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.totals.totalExpenses)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatNumber(content.parameters?.totalArea ? (content.totals.totalExpenses / (content.parameters.totalArea * ((new Date(content.parameters.periodEnd).getTime() - new Date(content.parameters.periodStart).getTime()) / (1000 * 60 * 60 * 24 * 30)))) : 0)}</td>
</tr>
<tr>
<td colSpan={4} className="py-2"></td>
</tr>
<tr>
<td className="py-2 px-3 text-sm text-slate-700">НДС</td>
<td colSpan={2} className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.vat)}</td>
<td></td>
</tr>
{content.totals.recalculation > 0 && (
<tr>
<td className="py-2 px-3 text-sm text-slate-700">Перерасчет (механизированная уборка, резервный фонд)</td>
<td colSpan={2} className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.recalculation)}</td>
<td></td>
</tr>
)}
<tr>
<td className="py-2 px-3 text-sm font-medium text-slate-700">Итого расходов с учетом перерасчета (без НДС)</td>
<td colSpan={2} className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.totals.totalWithRecalculation)}</td>
<td></td>
</tr>
<tr>
<td className="py-2 px-3 text-sm font-medium text-slate-700">Итого расходов с учетом перерасчета</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.totalWithRecalculationWithVAT / (content.parameters?.totalArea ? (new Date(content.parameters.periodEnd).getTime() - new Date(content.parameters.periodStart).getTime()) / (1000 * 60 * 60 * 24 * 30) : 1))}</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.totals.totalWithRecalculationWithVAT)}</td>
<td></td>
</tr>
<tr>
<td className="py-2 px-3 text-sm text-slate-700">Возврат долга</td>
<td colSpan={2} className="py-2 px-3 text-sm text-right text-slate-700">{content.totals.debtReturn > 0 ? formatCurrency(content.totals.debtReturn) : ''}</td>
<td></td>
</tr>
<tr>
<td className="py-2 px-3 text-sm font-bold text-slate-800">Итого тариф</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.totalTariff / (content.parameters?.totalArea ? (new Date(content.parameters.periodEnd).getTime() - new Date(content.parameters.periodStart).getTime()) / (1000 * 60 * 60 * 24 * 30) : 1))}</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.totals.totalTariff)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatNumber(content.parameters?.totalArea ? (content.totals.totalTariff / (content.parameters.totalArea * ((new Date(content.parameters.periodEnd).getTime() - new Date(content.parameters.periodStart).getTime()) / (1000 * 60 * 60 * 24 * 30)))) : 0)}</td>
</tr>
{content.totals.otherIncome > 0 && (() => {
const periodMonths = content.parameters?.totalArea
? (new Date(content.parameters.periodEnd).getTime() - new Date(content.parameters.periodStart).getTime()) / (1000 * 60 * 60 * 24 * 30)
: 1;
const otherIncomePerM2 = content.parameters?.totalArea
? content.totals.otherIncome / (content.parameters.totalArea * periodMonths)
: 0;
return (
<tr>
<td className="py-2 px-3 text-sm text-slate-700">Прочие доходы</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.otherIncome / periodMonths)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatCurrency(content.totals.otherIncome)}</td>
<td className="py-2 px-3 text-sm text-right text-slate-700">{formatNumber(otherIncomePerM2)}</td>
</tr>
);
})()}
</tbody>
</table>
</div>
)}
{/* Финансовые результаты */}
{content.financialResults && (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none overflow-x-auto">
<h3 className="text-lg font-bold text-slate-800 mb-4">Финансовый результат</h3>
<table className="w-full border-collapse">
<tbody>
<tr>
<td colSpan={4} className="py-2 px-3 text-sm font-bold text-slate-800">Финансовый результат по содержанию</td>
</tr>
<tr>
<td colSpan={2} className="py-2 px-3 text-sm text-slate-700">исходя из начисленых</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.financialResults.maintenanceFromAccrued || 0)}</td>
<td className={`py-2 px-3 text-sm text-right font-bold ${
(content.financialResults.maintenanceFromAccrued || 0) < 0 ? 'text-red-600' : 'text-slate-800'
}`}>
{formatNumber(content.parameters?.totalArea ? ((content.financialResults.maintenanceFromAccrued || 0) / content.parameters.totalArea) : 0)}
</td>
</tr>
<tr>
<td colSpan={2} className="py-2 px-3 text-sm text-slate-700">исходя из поступивших средств</td>
<td className={`py-2 px-3 text-sm text-right font-bold ${
(content.financialResults.maintenanceFromReceived || 0) < 0 ? 'text-red-600' : 'text-slate-800'
}`}>
{formatCurrency(content.financialResults.maintenanceFromReceived || 0)}
</td>
<td className={`py-2 px-3 text-sm text-right font-bold ${
(content.financialResults.maintenanceFromReceived || 0) < 0 ? 'text-red-600' : 'text-slate-800'
}`}>
{formatNumber(content.parameters?.totalArea ? ((content.financialResults.maintenanceFromReceived || 0) / content.parameters.totalArea) : 0)}
</td>
</tr>
<tr>
<td colSpan={4} className="py-2"></td>
</tr>
<tr>
<td colSpan={4} className="py-2 px-3 text-sm font-bold text-slate-800">Финансовый результат по резервному фонду</td>
</tr>
<tr>
<td colSpan={2} className="py-2 px-3 text-sm text-slate-700">исходя из начисленых</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.financialResults.reserveFundFromAccrued || 0)}</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">
{formatNumber(content.parameters?.totalArea ? ((content.financialResults.reserveFundFromAccrued || 0) / content.parameters.totalArea) : 0)}
</td>
</tr>
<tr>
<td colSpan={2} className="py-2 px-3 text-sm text-slate-700">исходя из поступивших средств</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">{formatCurrency(content.financialResults.reserveFundFromReceived || 0)}</td>
<td className="py-2 px-3 text-sm text-right font-bold text-slate-800">
{formatNumber(content.parameters?.totalArea ? ((content.financialResults.reserveFundFromReceived || 0) / content.parameters.totalArea) : 0)}
</td>
</tr>
</tbody>
</table>
</div>
)}
{/* Подпись директора */}
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm p-6 print:border-0 print:shadow-none">
<div className="flex justify-end">
<div className="text-right">
<p className="text-sm font-bold text-slate-800 mb-1">Директор</p>
<p className="text-sm text-slate-600">_________________</p>
</div>
</div>
</div>
</div>
);
};