first commit
This commit is contained in:
207
resources/js/pages/reports/analysis/Index.vue
Normal file
207
resources/js/pages/reports/analysis/Index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user