chore: initial import for test contour

This commit is contained in:
sova-bootstrap
2026-05-27 19:36:33 +03:00
commit ffd4cf9031
105 changed files with 10772 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useSelector } from 'react-redux';
import { apiUrl } from '@/config/api';
import { useGetFilialsQuery } from '../api/apiFilial';
import { useGetDepartmentsQuery } from '../api/apiDepartment';
import { selectRegions } from '../store/slice/regionSlice';
import { useGetEmptyLocationsQuery } from '../api/apiLocation';
export function useLostDoctors() {
const { data, isLoading, error } = useGetEmptyLocationsQuery();
const [ lostDoctors, setLostDoctors ] = useState([]);
const filialsQuery = useGetFilialsQuery();
const filials = filialsQuery.data ?? [];
const departmentsQuery = useGetDepartmentsQuery();
const departments = departmentsQuery.data ?? [];
const regions = useSelector(selectRegions);
const fetchParams = [ 'filial', 'dcode', 'department' ];
useEffect(() => {
if (!data?.data.length) {
setLostDoctors([]);
return;
}
let canceled = false;
Promise.all(
data.data.map(item => {
const fetchString = fetchParams.filter((param => Boolean(item[param]))).map(param => `${param}=${item[param]}`).join('&');
return axios.get(apiUrl(`/idoctor/list?${fetchString}`), {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
})
.then(res => {
return ({ doctor: res?.data?.data[0], emptyLocation: item });
})
}
)
).then(results => {
if (!canceled) {
setLostDoctors(results.filter(Boolean));
}
}).catch(() => {
if (!canceled) {
setLostDoctors([]);
}
});
return () => { canceled = true };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
return {
isLoading,
error,
lostDoctors,
filials: filials?.data || [],
departments: departments?.data || [],
regions,
};
}
+19
View File
@@ -0,0 +1,19 @@
import { useSelector } from 'react-redux';
import { useGetSpecialistsQuery } from '../api/apiSpecialist';
import { selectRegions } from '../store/slice/regionSlice';
export function useNewSpecialistId() {
const regions = useSelector(selectRegions);
const regionIds = Object.keys(regions);
const queries = regionIds.map(rid => useGetSpecialistsQuery(rid));
const isLoading = queries.some(q => q.isLoading);
const error = queries.find(q => q.error)?.error;
const allData = queries.flatMap(q => q.data ?? []);
const newId = Math.max.apply(Math,
allData.map(( specialist ) => Number(specialist.id))
) + 1;
return newId
}
+13
View File
@@ -0,0 +1,13 @@
import React from "react";
export const useOutsideClick = (ref, onOutsideClick) => {
React.useEffect(() => {
const handleClick = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
onOutsideClick();
}
};
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
}, [ref, onOutsideClick]);
}
+43
View File
@@ -0,0 +1,43 @@
import { useMemo } from 'react';
export const useSortedPaginated = (data, itemsPerPage, sortBy, sortDirection, currentPage) => {
const sorted = useMemo(() => {
return [...data].sort((a, b) => {
let aValue = a[sortBy], bValue = b[sortBy];
if (sortBy === 'experience') {
aValue = typeof aValue === 'number' ? aValue : 0;
bValue = typeof bValue === 'number' ? bValue : 0;
}
if (sortBy === 'locations') {
aValue = aValue === 'нет данных' ? 'я' : aValue;
bValue = bValue === 'нет данных' ? 'я' : bValue;
}
if (aValue == null || bValue == null) return 0
if (typeof aValue === 'number') return sortDirection === 'asc'
? aValue - bValue
: bValue - aValue
if (typeof aVal === 'string' && typeof bVal === 'string') {
aValue = aValue.replaceAll(' ', '');
bValue = bValue.replaceAll(' ', '');
}
return sortDirection === 'asc'
? String(aValue).localeCompare(String(bValue))
: String(bValue).localeCompare(String(aValue))
});
}, [data, sortBy, sortDirection]);
const totalPages = Math.ceil(sorted.length / itemsPerPage);
const paginated = useMemo(() => {
const start = (currentPage - 1) * itemsPerPage;
return sorted.slice(start, start + itemsPerPage);
}, [sorted, currentPage, itemsPerPage]);
return { paginated, totalPages };
}
+16
View File
@@ -0,0 +1,16 @@
import { useCallback, useState } from "react";
export const useSorting = (initialKey = 'id', initialDir = 'asc') => {
const [sortBy, setSortBy] = useState(initialKey);
const [sortDirection, setSortDirection] = useState(initialDir);
const handleSort = useCallback(
key => {
setSortDirection(prev => (sortBy === key ? (prev === 'asc' ? 'desc' : 'asc') : 'asc'));
setSortBy(key);
},
[sortBy]
);
return { sortBy, sortDirection, handleSort };
}
+60
View File
@@ -0,0 +1,60 @@
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useSelector } from 'react-redux';
import { apiUrl } from '@/config/api';
import { useGetSpecialistsQuery, useGetSpecialistQuery } from '../api/apiSpecialist';
import { useGetKodopersQuery } from '../api/apiKodoper';
import { useGetFilialsQuery } from '../api/apiFilial';
import { useGetDepartmentsQuery } from '../api/apiDepartment';
import { selectRegions } from '../store/slice/regionSlice';
export function useSpecialist(id) {
const { data: specialist, isLoading, error } = useGetSpecialistQuery(id);
const [kodoperDetails, setKodoperDetails] = useState([]);
const filialsQuery = useGetFilialsQuery();
const filials = filialsQuery.data ?? [];
const departmentsQuery = useGetDepartmentsQuery();
const departments = departmentsQuery.data ?? [];
const regions = useSelector(selectRegions);
useEffect(() => {
if (!specialist?.kodoper?.length) {
setKodoperDetails([]);
return;
}
let canceled = false;
Promise.all(
specialist.kodoper.map(code =>
axios.get(apiUrl(`/pricelist/list?search=${code}`), {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
})
.then(res => {
return (res.data.data || []).find(item => String(item.kodoper) === String(code)) || null;
})
)
).then(results => {
if (!canceled) {
setKodoperDetails(results.filter(Boolean));
}
}).catch(() => {
if (!canceled) {
setKodoperDetails([]);
}
});
return () => { canceled = true };
}, [specialist]);
return {
isLoading,
error,
specialist: specialist ?? null,
filials,
departments,
regions,
kodoperDetails,
};
}
+43
View File
@@ -0,0 +1,43 @@
import { useMemo } from "react";
export function useSpecialistFilter({ regionId, filialId, searchValue }, queries, filials, regionsMap, currentYear) {
return useMemo(() => {
const flatData =
regionId === 'all'
? queries.flatMap(q => q.data || [])
: queries.find(q => q.originalArgs === regionId)?.data || [];
console.log(queries)
const formatted = flatData.map(s => {
const experience = s.experience ? currentYear - s.experience : null;
const location = s.locations[0];
const filial = filials.find(f => f.id === location?.filial);
return {
...s,
experience,
locations: filial ? `${regionsMap[filial.regionId]}, ${filial.shortName}` : 'нет данных',
filialId: filial?.id || null,
activeLabel: s.active ? 'да' : 'нет',
scheduleLabel: s.displaySchedule ? 'да' : 'нет',
};
});
const byFilial = filialId === 'all' ? formatted : formatted.filter(s => s.filialId === Number(filialId));
const search = searchValue.trim().toLowerCase().replaceAll(' ', '');
if (!search) return byFilial;
return byFilial.filter(s => {
if ( String(s.id).includes(search) ) return true
if ( s.nameString.replaceAll(' ', '').toLowerCase().includes(search) ) return true
if ( s.experience ) {
if ( String(s.experience ?? '').includes(search) ) return true
}
if ( s.post ) {
if ( s.post.replaceAll(' ', '').toLowerCase().includes(search) ) return true
}
return false
}
);
}, [regionId, filialId, searchValue, queries, filials, regionsMap, currentYear]);
}