Initial commit.

This commit is contained in:
Kirill Kirilenko 2026-01-13 18:29:50 +03:00
commit ef812f7fd9
3 changed files with 280 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea

BIN
favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

279
index.html Normal file
View file

@ -0,0 +1,279 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Проверка наличия льготных препаратов</title>
<link rel="icon" type="image/png" href="/favicon.png">
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 700px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h2 {
color: #333;
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
th {
background-color: #195695;
color: white;
}
u {
border-bottom: 1px dashed #cccccc;
text-decoration: none;
cursor: help;
}
.name {
background-color: #e6f2ff;
text-align: center;
color: #195695;
}
.loading {
text-align: center;
padding: 20px;
font-style: italic;
color: #666;
}
.error {
color: red;
text-align: center;
padding: 20px;
}
.note {
margin-top: 10px;
font-size: small;
text-align: center;
}
.search-container {
display: flex;
gap: 10px;
justify-content: center;
}
#searchInput {
flex: 1;
min-width: 220px;
max-width: 280px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
#searchButton {
padding: 10px 20px;
font-size: 16px;
background-color: #6d4203;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
#searchButton:hover {
background-color: #8f6f3f;
}
/* Responsive behavior for smaller screens */
@media (max-width: 600px) {
.search-container {
display: flex;
flex-direction: column;
gap: 10px;
flex-wrap: wrap;
align-items: stretch;
}
#searchInput {
align-self: center;
margin-bottom: 10px;
}
#searchButton {
align-self: center;
width: 90%;
}
}
@media (hover: none) {
u {
border-bottom: none !important;
}
}
</style>
</head>
<body>
<div class="container">
<h2>Проверка наличия льготных препаратов<br/>в аптеках Санкт-Петербурга</h2>
<div class="search-container">
<input type="text" id="searchInput" placeholder="Введите название препарата">
<button id="searchButton">Найти</button>
</div>
<div id="content"></div>
</div>
<script>
function fetchData(name) {
document.getElementById('content').innerHTML = '<div class="loading">Загрузка данных...</div>';
name = name.replaceAll(' ', '_');
fetch(`/api-proxy?operation=getMedicament&filter=${name}`)
.then(response => response.json())
.then(data => displayData(processData(data)))
.catch(error => showError(error));
}
// Extract needed data from JSON
function processData(data) {
if (data['status'] !== "SUCCESS")
return data['errors'];
const results = data['model']['result'];
let processedData = [];
results.forEach(result => {
result['districts'].forEach(district => {
district['apothecaries'].forEach(apothecary => {
let address = apothecary['address'];
const regex = /\d*, [а-яА-Я\-. ]+, (.+) \*.+/;
let m;
if ((m = regex.exec(address)) !== null)
address = m[1];
else
address = address.substring(0, address.indexOf('*'));
processedData.push({
name: result['name'],
apothecary: apothecary['name'] + '<br/><i>' + address + '</i>',
countFederal: apothecary['ost1'],
countRegional: apothecary['ost2'],
timestamp: apothecary['date']
});
});
});
});
return processedData;
}
// Display the data in a table format
function displayData(data) {
let htmlContent;
if (typeof(data) === 'object') {
htmlContent = `
<table>
<thead>
<tr>
<th>Аптека</th>
<th style="width: 75px; text-align: center;">Остаток<br/>(<u title="региональная льгота">рег</u>/<u title="федеральная льгота">фед</u>)</th>
</tr>
</thead>
<tbody>
`;
// Group consecutive items with same name and generate merged cells
if (data.length > 0) {
let currentName = data[0].name;
let rowCount = 1;
let rowStartIndex = 0;
for (let i = 1; i <= data.length; i++) {
if (i < data.length && data[i].name === currentName) {
rowCount++;
} else {
// Generate rows for the current group
htmlContent += '<tr><td colspan="2"></td></tr>';
htmlContent += `<tr><td colspan="2" class="name"><b>${currentName}</b></td></tr>`;
for (let j = rowStartIndex; j < i; j++) {
const item = data[j];
htmlContent += `
<tr>
<td>${item.apothecary}</td>
<td style="text-align: center;">${item.countRegional} / ${item.countFederal}</td>
</tr>`;
}
// Reset for next group
if (i < data.length) {
currentName = data[i].name;
rowCount = 1;
rowStartIndex = i;
}
}
}
}
htmlContent += `
</tbody>
</table>
`;
htmlContent += '<div class="note">Информация получена через <a href="https://eservice.gu.spb.ru/portalFront/resources/portal.html#medicament" target="_blank" style="text-decoration: none;">портал госуслуг Санкт-Петербурга</a>.</div>'
}
else
htmlContent = `<div class="error">${data}</div>`;
document.getElementById('content').innerHTML = htmlContent;
}
function showError(error) {
document.getElementById('content').innerHTML = `<div class="error">Ошибка: ${error.message}</div>`;
}
function search() {
const name = document.getElementById('searchInput').value.trim();
if (name) {
saveInputValue();
fetchData(name);
}
}
// Save input value to localStorage
function saveInputValue() {
const name = document.getElementById('searchInput').value;
localStorage.setItem('medicamentName', name);
}
// Restore input value from localStorage
function restoreInputValue() {
const savedValue = localStorage.getItem('medicamentName');
if (savedValue) {
document.getElementById('searchInput').value = savedValue;
return savedValue;
}
return null;
}
window.addEventListener('load', () => {
let name = restoreInputValue();
if (name !== null)
fetchData(name);
});
document.getElementById('searchButton').addEventListener('click', search);
document.getElementById('searchInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter')
search();
});
</script>
</body>
</html>