272 lines
10 KiB
Vue
272 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import { Form, Head } from '@inertiajs/vue3';
|
|
import { computed } from 'vue';
|
|
import Heading from '@/components/Heading.vue';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import {
|
|
index,
|
|
store,
|
|
} from '@/routes/reports/medication-expenses';
|
|
import type {
|
|
ExpenseCategoryOption,
|
|
FundingSourceOption,
|
|
MedicationExpenseTotals,
|
|
MedicationExpenseValueMap,
|
|
ReportPeriodSummary,
|
|
Team,
|
|
} from '@/types';
|
|
|
|
type Props = {
|
|
currentTeam?: Team | null;
|
|
periods: ReportPeriodSummary[];
|
|
departments: Array<{ id: number; name: string }>;
|
|
selectedPeriodId?: number | null;
|
|
selectedDepartmentId?: number | null;
|
|
fundingSources: FundingSourceOption[];
|
|
expenseCategories: ExpenseCategoryOption[];
|
|
values: MedicationExpenseValueMap;
|
|
totals: MedicationExpenseTotals;
|
|
canEdit: boolean;
|
|
};
|
|
|
|
const props = 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 totalCards = computed(() => [
|
|
{
|
|
key: 'total_without_dressing',
|
|
title: 'Без перевязки',
|
|
value: props.totals?.total_without_dressing ?? 0,
|
|
},
|
|
{
|
|
key: 'total_dressing',
|
|
title: 'Перевязка / ИМН',
|
|
value: props.totals?.total_dressing ?? 0,
|
|
},
|
|
{
|
|
key: 'total_expense',
|
|
title: 'Общий расход',
|
|
value: props.totals?.total_expense ?? 0,
|
|
},
|
|
{
|
|
key: 'without_budget_total',
|
|
title: 'Без бюджета',
|
|
value: props.totals?.without_budget_total ?? 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="Аналог листа Расход-2026: ввод первичных сумм по источникам финансирования и категориям расхода."
|
|
/>
|
|
|
|
<Card class="border-sidebar-border/70">
|
|
<CardHeader>
|
|
<CardTitle>Контекст ввода</CardTitle>
|
|
<CardDescription>
|
|
Выберите период и отделение для заполнения карточки расхода.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form
|
|
v-if="props.currentTeam"
|
|
v-bind="index.form(props.currentTeam.slug)"
|
|
class="grid gap-4 md:grid-cols-3"
|
|
>
|
|
<div class="grid gap-2">
|
|
<Label for="expense-period">Период</Label>
|
|
<select
|
|
id="expense-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="period in periods"
|
|
:key="period.id"
|
|
:value="period.id"
|
|
:selected="period.id === selectedPeriodId"
|
|
>
|
|
{{ period.label }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid gap-2">
|
|
<Label for="expense-department">Отделение</Label>
|
|
<select
|
|
id="expense-department"
|
|
name="department"
|
|
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
>
|
|
<option
|
|
v-for="department in departments"
|
|
:key="department.id"
|
|
:value="department.id"
|
|
:selected="
|
|
department.id === selectedDepartmentId
|
|
"
|
|
>
|
|
{{ department.name }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex items-end">
|
|
<Button type="submit">Открыть карточку</Button>
|
|
</div>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
<Card
|
|
v-for="card in totalCards"
|
|
:key="card.key"
|
|
class="border-sidebar-border/70"
|
|
>
|
|
<CardHeader class="pb-2">
|
|
<CardDescription>{{ card.title }}</CardDescription>
|
|
<CardTitle class="text-2xl">
|
|
{{ formatAmount(card.value) }}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
</Card>
|
|
</div>
|
|
|
|
<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>
|
|
Редактируются только первичные значения. Итоги ниже
|
|
считаются автоматически на сервере.
|
|
</CardDescription>
|
|
</div>
|
|
|
|
<Badge :variant="canEdit ? 'default' : 'secondary'">
|
|
{{ canEdit ? 'Черновик' : 'Утвержденный период' }}
|
|
</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form
|
|
v-if="props.currentTeam && selectedPeriodId && selectedDepartmentId"
|
|
v-bind="store.form(props.currentTeam.slug)"
|
|
class="space-y-6"
|
|
v-slot="{ processing }"
|
|
>
|
|
<input
|
|
type="hidden"
|
|
name="report_period_id"
|
|
:value="selectedPeriodId"
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="department_id"
|
|
:value="selectedDepartmentId"
|
|
/>
|
|
|
|
<div 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
|
|
v-for="category in expenseCategories"
|
|
:key="category.value"
|
|
class="px-3 py-2 text-left"
|
|
>
|
|
{{ category.label }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="source in fundingSources"
|
|
:key="source.value"
|
|
class="border-b"
|
|
>
|
|
<td class="px-3 py-3 font-medium">
|
|
{{ source.label }}
|
|
</td>
|
|
<td
|
|
v-for="category in expenseCategories"
|
|
:key="category.value"
|
|
class="px-3 py-3"
|
|
>
|
|
<Input
|
|
:name="`values[${source.value}][${category.value}]`"
|
|
type="number"
|
|
min="0"
|
|
step="0.01"
|
|
:disabled="!canEdit"
|
|
:default-value="
|
|
String(
|
|
values?.[source.value]?.[
|
|
category.value
|
|
] ?? 0,
|
|
)
|
|
"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<Button type="submit" :disabled="processing || !canEdit">
|
|
Сохранить расход
|
|
</Button>
|
|
</Form>
|
|
|
|
<p
|
|
v-else
|
|
class="py-6 text-center text-sm text-muted-foreground"
|
|
>
|
|
Создайте период и выберите отделение, чтобы начать ввод.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</template>
|