first commit
This commit is contained in:
271
resources/js/pages/reports/medication-expenses/Index.vue
Normal file
271
resources/js/pages/reports/medication-expenses/Index.vue
Normal file
@@ -0,0 +1,271 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user