diff --git a/app/Http/Controllers/Api/StatisticController.php b/app/Http/Controllers/Api/StatisticController.php
index 58975ad..460c8d7 100644
--- a/app/Http/Controllers/Api/StatisticController.php
+++ b/app/Http/Controllers/Api/StatisticController.php
@@ -125,4 +125,123 @@ class StatisticController extends Controller
return response()->json($deadPatients);
}
+
+ public function getTransferredPatients(Request $request)
+ {
+ $user = Auth::user();
+ $departmentType = $request->query('departmentType');
+ $availableDepartments = $user->misDepartments->pluck('rf_mis_department_id')->toArray();
+
+ $validated = $request->validate([
+ 'startAt' => 'required',
+ 'endAt' => 'required',
+ ]);
+
+ $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $validated['startAt'], $validated['endAt']);
+
+ $deadPatients = ReportDutyPatient::query()
+ ->whereHas('latestMigration', function ($q) use ($availableDepartments) {
+ $q->whereIn('department_id', $availableDepartments);
+ })
+ ->with('latestMigration')
+ ->where('death_date', '>', $dateRange->startSql())
+ ->where('death_date', '<=', $dateRange->endSql())
+ ->orderBy('death_date', 'desc')
+ ->get()->unique('original_id')->values()
+ ->map(fn ($row) => [
+ 'id' => $row->id,
+ 'full_name' => $row->full_name,
+ 'birth_date' => $row->birth_date,
+ 'ingoing_date' => $row->latestMigration->ingoing_date ?? $row->recipient_date,
+ 'diagnosis_code' => $row->latestMigration->diagnosis_code,
+ 'diagnosis_name' => $row->latestMigration->diagnosis_name,
+ 'department_name' => $row->latestMigration->department->name_full,
+ ]);
+
+ return response()->json($deadPatients);
+ }
+
+ public function getPatients(Request $request)
+ {
+ $user = Auth::user();
+ $departmentType = $request->query('departmentType');
+ $availableDepartments = $user->misDepartments->pluck('rf_mis_department_id')->toArray();
+ $validated = $request->validate([
+ 'startAt' => 'required',
+ 'endAt' => 'required',
+ 'type' => 'required',
+ 'departmentId' => 'nullable'
+ ]);
+ $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $validated['startAt'], $validated['endAt']);
+
+ $query = ReportDutyPatient::join('report_duties', 'report_duty_patients.report_duty_id', '=', 'report_duties.id')
+ ->where('report_duties.period_start', '>=', $dateRange->startSql())
+ ->where('report_duties.period_start', '<=', $dateRange->endSql())
+ ->select('report_duty_patients.*')
+ ->when(isset($validated['departmentId']), function ($query) use ($validated) {
+ return $query->where('rf_department_id', $validated['departmentId']);
+ })
+ ->distinct(['original_id']);
+
+ $filterQuery = $this->getQueryByType($validated['type']);
+ if (isset($validated['type'])) {
+ $filterQuery($query, $dateRange->startDate, $dateRange->endDate);
+ }
+
+ $patients = $query->get();
+
+ return response()->json($patients);
+ }
+
+ private function getQueryByType($type) {
+ $types = [
+ 'RECIPIENT_ALL' => function ($query, $start, $end) {
+ $query->where('report_duty_patients.recipient_date', '>=', $start)
+ ->where('report_duty_patients.recipient_date', '<=', $end);
+ },
+ 'RECIPIENT_PLAN' => function ($query, $start, $end) {
+ $query->where('report_duty_patients.urgency_id', 1);
+ },
+ 'RECIPIENT_EMERGENCY' => function ($query, $start, $end) {
+ $query->where('report_duty_patients.recipient_date', '>', $start)
+ ->where('report_duty_patients.recipient_date', '<', $end)
+ ->where('report_duty_patients.urgency_id', 2);
+ },
+ 'RECIPIENT_TRANSFERRED' => function ($query, $start, $end) {
+ $query->whereIn('report_duty_patients.visit_result_id', [4, 14]);
+ },
+ 'OUTCOME' => function ($query, $start, $end) {
+ $query->whereBetween('report_duty_patients.extract_date', [$start, $end]);
+ },
+ 'CONSIST' => function ($query, $start, $end) {
+ // Пациенты, которые числятся в отчётах за период и не выписаны/не умерли на конец периода
+ $query->where(function ($q) use ($end) {
+ $q->whereNull('report_duty_patients.extract_date')
+ ->orWhere('report_duty_patients.extract_date', '>', $end);
+ })
+ ->where(function ($q) use ($end) {
+ $q->whereNull('report_duty_patients.death_date')
+ ->orWhere('report_duty_patients.death_date', '>', $end);
+ });
+ },
+ 'DECEASED' => function ($query, $start, $end) {
+ $query->whereBetween('report_duty_patients.death_date', [$start, $end]);
+ },
+ 'SURGICAL_PLAN' => function ($query, $start, $end) {
+ $query->whereHas('operations', function ($q) use ($start, $end) {
+ $q->whereBetween('surgical_operations.Date', [$start, $end])
+ ->where('surgical_operations.type', 'plan');
+ });
+ },
+ 'SURGICAL_EMERGENCY' => function ($query, $start, $end) {
+ $query->whereHas('operations', function ($q) use ($start, $end) {
+ $q->whereBetween('surgical_operations.Date', [$start, $end])
+ ->where('surgical_operations.type', 'emergency');
+ });
+ },
+ // ... остальные
+ ];
+
+ return $types[$type];
+ }
}
diff --git a/resources/js/Pages/Statistic/Components/InfoModal/PatientListModal.vue b/resources/js/Pages/Statistic/Components/InfoModal/PatientListModal.vue
new file mode 100644
index 0000000..6b4662f
--- /dev/null
+++ b/resources/js/Pages/Statistic/Components/InfoModal/PatientListModal.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/Pages/Statistic/Components/InfoModal/columns/index.js b/resources/js/Pages/Statistic/Components/InfoModal/columns/index.js
new file mode 100644
index 0000000..2808bf9
--- /dev/null
+++ b/resources/js/Pages/Statistic/Components/InfoModal/columns/index.js
@@ -0,0 +1,53 @@
+// columns/index.js
+import { h } from 'vue'
+import { formatDistanceStrict } from 'date-fns'
+import { ru } from 'date-fns/locale'
+import TooltipColumn from '../../../../Report/Components/DataTableColumns/TooltipColumn.vue'
+
+export function getColumns(type) {
+ // Базовые общие колонки
+ const base = [
+ { title: '№', key: 'index', width: 30, render: (_, i) => i + 1 },
+ { title: 'ФИО', key: 'full_name', width: 200, ellipsis: { tooltip: { arrow: false } } },
+ { title: 'Возраст', key: 'age', width: 80, render: row => row.birth_date
+ ? formatDistanceStrict(new Date(row.birth_date), new Date(), { locale: ru })
+ : '—' },
+ { title: 'Диагноз', key: 'diagnosis', width: 90, render: row =>
+ row.diagnosis_code
+ ? h(TooltipColumn, { triggerText: row.diagnosis_code, contentText: row.diagnosis_name })
+ : '—' },
+ { title: 'Отделение', key: 'department_name', width: 220 },
+ ]
+
+ // Дополнительные колонки для конкретных типов
+ const extra = {
+ RECIPIENT_ALL: [
+ { title: 'Дата поступления', key: 'admission_date', width: 130 },
+ { title: 'Тип', key: 'admission_type', width: 100 }, // планово/экстр
+ ],
+ RECIPIENT_PLAN: [
+ { title: 'Дата поступления', key: 'admission_date', width: 130 },
+ { title: 'Тип', key: 'admission_type', width: 100 }, // планово/экстр
+ ],
+ RECIPIENT_EMERGENCY: [
+ { title: 'Дата поступления', key: 'admission_date', width: 130 },
+ { title: 'Тип', key: 'admission_type', width: 100 }, // планово/экстр
+ ],
+ SURGICAL_EMERGENCY: [
+ { title: 'Дата операции', key: 'operation_date', width: 130 },
+ { title: 'Хирург', key: 'surgeon', width: 150 },
+ { title: 'Операция', key: 'operation_name', width: 200 },
+ ],
+ SURGICAL_PLAN: [
+ { title: 'Дата операции', key: 'operation_date', width: 130 },
+ { title: 'Хирург', key: 'surgeon', width: 150 },
+ { title: 'Операция', key: 'operation_name', width: 200 },
+ ],
+ DECEASED: [
+ { title: 'Дата смерти', key: 'death_date', width: 120 },
+ { title: 'Причина смерти', key: 'cause_of_death', width: 150 },
+ ],
+ }
+
+ return [...base, ...(extra[type] || [])]
+}
diff --git a/resources/js/Pages/Statistic/Components/InfoModal/config.js b/resources/js/Pages/Statistic/Components/InfoModal/config.js
new file mode 100644
index 0000000..43dadae
--- /dev/null
+++ b/resources/js/Pages/Statistic/Components/InfoModal/config.js
@@ -0,0 +1,72 @@
+export const MODAL_TYPES = {
+ RECIPIENT_ALL: {
+ title: 'Поступившие',
+ endpoint: '/api/statistics/reports/admitted-patients',
+ columnsFactory: 'admittedColumns',
+ },
+ RECIPIENT_PLAN: {
+ title: 'Плановые поступления',
+ endpoint: '/api/statistics/reports/admitted-patients',
+ columnsFactory: 'admittedColumns',
+ },
+ RECIPIENT_EMERGENCY: {
+ title: 'Экстренные поступления',
+ endpoint: '/api/statistics/reports/emergency-patients',
+ columnsFactory: 'emergencyColumns',
+ },
+ RECIPIENT_TRANSFERRED: {
+ title: 'Переведённые',
+ endpoint: '/api/statistics/reports/transferred-patients',
+ columnsFactory: 'transferredColumns',
+ },
+ OUTCOME: {
+ title: 'Выбывшие',
+ endpoint: '/api/statistics/reports/discharged-patients',
+ columnsFactory: 'dischargedColumns',
+ },
+ CONSIST: {
+ title: 'Состоит',
+ endpoint: '/api/statistics/reports/admitted-patients',
+ columnsFactory: 'admittedColumns',
+ },
+ AVERAGE_BED_DAYS: {
+ title: 'Средний койко-день',
+ endpoint: '/api/statistics/reports/admitted-patients',
+ columnsFactory: 'admittedColumns',
+ },
+ PREOPERATIVE_DAYS: {
+ title: 'Предоперационный койко-день',
+ endpoint: '/api/statistics/reports/admitted-patients',
+ columnsFactory: 'admittedColumns',
+ },
+ PERCENT_LOADED_BEDS: {
+ title: 'Процент загруженности',
+ endpoint: '/api/statistics/reports/admitted-patients',
+ columnsFactory: 'admittedColumns',
+ },
+ LETHALITY: {
+ title: 'Процент летальности',
+ endpoint: '/api/statistics/reports/dead-patients',
+ columnsFactory: 'deadColumns', // или функция
+ },
+ SURGICAL_EMERGENCY: {
+ title: 'Экстренные операции',
+ endpoint: '/api/statistics/reports/dead-patients',
+ columnsFactory: 'deadColumns', // или функция
+ },
+ SURGICAL_PLAN: {
+ title: 'Плановые операции',
+ endpoint: '/api/statistics/reports/dead-patients',
+ columnsFactory: 'deadColumns', // или функция
+ },
+ DECEASED: {
+ title: 'Умершие',
+ endpoint: '/api/statistics/reports/dead-patients',
+ columnsFactory: 'deadColumns', // или функция
+ },
+ COUNT_STAFF: {
+ title: 'Дежурный персонал',
+ endpoint: '/api/statistics/reports/dead-patients',
+ columnsFactory: 'deadColumns', // или функция
+ },
+}
diff --git a/resources/js/Pages/Statistic/Index.vue b/resources/js/Pages/Statistic/Index.vue
index 2fa174d..29d0c20 100644
--- a/resources/js/Pages/Statistic/Index.vue
+++ b/resources/js/Pages/Statistic/Index.vue
@@ -25,6 +25,7 @@ import {percentType} from "../../Utils/numbers.js";
import OutcomeColumn from "./Components/OutcomeColumn.vue";
import ModalDeathPatients from "./Components/ModalDeathPatients.vue";
import DeceasedColumn from "./Components/DeceasedColumn.vue";
+import PatientListModal from "./Components/InfoModal/PatientListModal.vue";
const props = defineProps({
data: {
@@ -150,6 +151,11 @@ const columns = ref([
width: 60,
titleAlign: 'center',
align: 'center',
+ cellProps: (row, i) => ({
+ onClick: () => {
+ onShowPatientsModal('RECIPIENT_ALL', row.department_id)
+ }
+ })
},
{
title: 'План',
@@ -157,6 +163,11 @@ const columns = ref([
width: 60,
titleAlign: 'center',
align: 'center',
+ cellProps: (row, i) => ({
+ onClick: () => {
+ onShowPatientsModal('RECIPIENT_PLAN', row.department_id)
+ }
+ })
},
{
title: 'Экстр',
@@ -164,6 +175,11 @@ const columns = ref([
width: 60,
titleAlign: 'center',
align: 'center',
+ cellProps: (row, i) => ({
+ onClick: () => {
+ onShowPatientsModal('RECIPIENT_EMERGENCY', row.department_id)
+ }
+ })
},
{
title: 'Перевод',
@@ -171,6 +187,11 @@ const columns = ref([
width: 84,
titleAlign: 'center',
align: 'center',
+ cellProps: (row, i) => ({
+ onClick: () => {
+ onShowPatientsModal('RECIPIENT_TRANSFERRED', row.department_id)
+ }
+ })
},
]
},
@@ -281,6 +302,14 @@ const currentDepartmentId = ref(null)
const showUnwantedEventsModal = ref(false)
const showObservablePatientsModal = ref(false)
const showDeathPatientsModal = ref(false)
+const patientsModalType = ref(null)
+const showPatientsModalType = ref(false)
+
+const onShowPatientsModal = (type, departmentId) => {
+ patientsModalType.value = type
+ currentDepartmentId.value = departmentId
+ showPatientsModalType.value = true
+}
const onShowUnwantedEventsModal = (departmentId) => {
currentDepartmentId.value = departmentId
@@ -399,6 +428,9 @@ const buildReportHref = (departmentId, startAt, endAt) => {
:start-at="date[0]" :end-at="date[1]" />
+
+
diff --git a/resources/js/app.js b/resources/js/app.js
index 9cdfd0d..32129bf 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -94,12 +94,35 @@ createInertiaApp({
sendDefaultPii: true,
integrations: [
Sentry.replayIntegration(),
+ // Sentry.feedbackIntegration({
+ // colorScheme: "system",
+ // showBranding: false,
+ // triggerLabel: "Сообщить об ошибке",
+ // triggerAriaLabel: "Открыть форму обратной связи",
+ // formTitle: "Сообщить об ошибке",
+ // submitButtonLabel: "Отправить отчёт",
+ // cancelButtonLabel: "Отмена",
+ // confirmButtonLabel: "Подтвердить",
+ // addScreenshotButtonLabel: "Добавить скриншот",
+ // removeScreenshotButtonLabel: "Удалить скриншот",
+ // nameLabel: "Имя",
+ // namePlaceholder: "Ваше имя",
+ // emailLabel: "Email",
+ // emailPlaceholder: "ваш.email@пример.рф",
+ // isRequiredLabel: "(обязательно)",
+ // messageLabel: "Описание проблемы",
+ // messagePlaceholder: "В чём ошибка? Что вы ожидали?",
+ // successMessageText: "Спасибо за ваш отчёт!",
+ // highlightToolText: "Выделить",
+ // hideToolText: "Скрыть",
+ // removeHighlightText: "Убрать",
+ // }),
],
// Session Replay
replaysSessionSampleRate: 0, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 0.5, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.,
// Logs
- enableLogs: true
+ enableLogs: true,
});
vueApp.mount(el)
diff --git a/routes/api.php b/routes/api.php
index 2ecae44..6d7d371 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -58,6 +58,8 @@ Route::middleware(['auth:sanctum'])->group(function () {
Route::get('/unwanted-events', [\App\Http\Controllers\Api\StatisticController::class, 'getUnwantedEvents']);
Route::get('/observable-patients', [\App\Http\Controllers\Api\StatisticController::class, 'getObservablePatients']);
Route::get('/dead-patients', [\App\Http\Controllers\Api\StatisticController::class, 'getDeadPatients']);
+ Route::get('/transferred-patients', [\App\Http\Controllers\Api\StatisticController::class, 'getTransferredPatients']);
+ Route::get('/patients', [\App\Http\Controllers\Api\StatisticController::class, 'getPatients']);
});
Route::get('/headquarters', [\App\Http\Controllers\Api\HeadquartersController::class, 'stats']);
});