first commit
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
brusnitsyn
2026-04-06 00:06:00 +09:00
commit fb2e6c58e3
409 changed files with 42953 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
<script setup lang="ts">
import { Form, Head } from '@inertiajs/vue3';
import Heading from '@/components/Heading.vue';
import { Badge } from '@/components/ui/badge';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { index } from '@/routes/reports/analysis';
import type {
AnalysisColumn,
AnalysisRow,
ReportPeriodSummary,
Team,
} from '@/types';
type Props = {
currentTeam?: Team | null;
periods: ReportPeriodSummary[];
selectedPeriodId?: number | null;
period?: { id: number; year: number; month: number; label: string } | null;
columns: AnalysisColumn[];
rows: AnalysisRow[];
meta: {
status?: string | null;
statusLabel?: string | null;
updatedAt?: string | null;
canEditSources: boolean;
};
};
defineProps<Props>();
defineOptions({
layout: (props: { currentTeam?: Team | null }) => ({
breadcrumbs: [
{
title: 'Отчеты',
href: props.currentTeam ? index(props.currentTeam.slug) : '/',
},
{
title: 'Анализ',
href: props.currentTeam ? index(props.currentTeam.slug) : '/',
},
],
}),
});
const resolveValue = (row: AnalysisRow, key: string) => {
const segments = key.split('.');
let current: unknown = row;
for (const segment of segments) {
if (
current !== null &&
typeof current === 'object' &&
segment in current
) {
current = current[segment as keyof typeof current];
continue;
}
return 0;
}
return typeof current === 'number' ? current : 0;
};
const formatAmount = (value: number) =>
new Intl.NumberFormat('ru-RU', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(value);
</script>
<template>
<Head title="Анализ" />
<div class="flex flex-col gap-6">
<Heading
title="Анализ"
description="Итоговый отчет, который собирает затраты по оказанным услугам и расход медикаментов по выбранному периоду."
/>
<Card class="border-sidebar-border/70">
<CardHeader>
<div
class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"
>
<div>
<CardTitle>Параметры отчета</CardTitle>
<CardDescription>
{{
period
? `Открыт период: ${period.label}`
: 'Выберите период для построения отчета.'
}}
</CardDescription>
</div>
<Badge :variant="meta.canEditSources ? 'default' : 'secondary'">
{{ meta.statusLabel ?? 'Нет периода' }}
</Badge>
</div>
</CardHeader>
<CardContent>
<Form
v-if="currentTeam"
v-bind="index.form(currentTeam.slug)"
class="flex flex-col gap-4 md:flex-row md:items-end"
>
<div class="grid gap-2 md:min-w-72">
<label for="analysis-period" class="text-sm font-medium">
Период
</label>
<select
id="analysis-period"
name="period"
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
>
<option
v-for="reportPeriod in periods"
:key="reportPeriod.id"
:value="reportPeriod.id"
:selected="
reportPeriod.id === selectedPeriodId
"
>
{{ reportPeriod.label }}
</option>
</select>
</div>
<Button type="submit">Построить</Button>
</Form>
</CardContent>
</Card>
<Card class="border-sidebar-border/70">
<CardHeader>
<CardTitle>Таблица анализа</CardTitle>
<CardDescription>
Строк: {{ rows.length }}
</CardDescription>
</CardHeader>
<CardContent>
<div v-if="rows.length > 0" class="overflow-x-auto">
<table class="min-w-full border-collapse text-sm">
<thead>
<tr class="border-b">
<th class="px-3 py-2 text-left">Отделение</th>
<th class="px-3 py-2 text-left">Профиль</th>
<th
v-for="column in columns"
:key="column.key"
class="px-3 py-2 text-left"
>
<div class="flex flex-col">
<span>{{ column.label }}</span>
<span
v-if="column.unit"
class="text-xs text-muted-foreground"
>
{{ column.unit }}
</span>
</div>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in rows"
:key="row.department.id"
class="border-b align-top"
>
<td class="px-3 py-3 font-medium">
{{ row.department.name }}
</td>
<td class="px-3 py-3 text-muted-foreground">
{{ row.department.profileName }}
</td>
<td
v-for="column in columns"
:key="column.key"
class="px-3 py-3"
>
{{ formatAmount(resolveValue(row, column.key)) }}
</td>
</tr>
</tbody>
</table>
</div>
<p
v-else
class="py-6 text-center text-sm text-muted-foreground"
>
Пока нет данных для выбранного периода.
</p>
</CardContent>
</Card>
</div>
</template>