Files
mkd/components/pr/ResidentReportView.tsx
2026-02-04 00:17:04 +05:00

411 lines
24 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 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>
);
};