Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class WebSecurityConfig(
.requestMatchers(
HttpMethod.GET,
"/",
"/offline.html",
"/manifest.json",
"/register",
"/components/**",
"/styles/**",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package pro.qyoga.app.publc.surverys

import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import pro.qyoga.core.clients.cards.ClientsRepo
import pro.qyoga.core.clients.cards.findByPhone
import pro.qyoga.core.clients.cards.model.PhoneNumber
import pro.qyoga.core.clients.cards.model.toLogString
import pro.qyoga.core.survey_forms.settings.model.SurveyFormsSettingsRepo
import pro.qyoga.core.survey_forms.settings.model.findByYandexAdminEmail
import java.time.LocalDate
Expand All @@ -16,15 +18,24 @@ class ProcessSurveyOp(
private val surveyFormsSettingsRepo: SurveyFormsSettingsRepo
) : (SurveyRq) -> Unit {

private val log = LoggerFactory.getLogger(javaClass)

@Transactional
override operator fun invoke(surveyRq: SurveyRq) {
log.debug("Processing survey: {}", surveyRq)
val therapistRef = surveyFormsSettingsRepo.findByYandexAdminEmail(surveyRq.yandexAdminEmail)
?.therapistRef
?: throw InvalidSurveyException.surveySettingsNotFoundForAdminEmail()

var client = clientsRepo.findByPhone(therapistRef, PhoneNumber.of(surveyRq.survey.phone))
?: surveyRq.survey.toClient(therapistRef)

if (client.version > 0) {
log.info("Updating client: {} from survey", client.toLogString())
} else {
log.info("Creating client: {} from survey", client.toLogString())
}

val today = LocalDate.now()
client = client
.prependComplaints(formatComplaintsEntry(surveyRq, today))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@ private fun prependTextBlock(value: String?, base: String?): String? {
}

return value.trimEnd('\n') + "\n\n" + base
}
}

fun Client.toLogString() =
"$lastName $firstName ($id)"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions app/src/main/resources/static/js/pwa/pwa-register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/js/pwa/sw.js").then(
(registration) => {
console.log("Service worker registration successful:", registration);
},
(error) => {
console.error(`Service worker registration failed: ${error}`);
},
);
} else {
console.error("Service workers are not supported.");
}
58 changes: 58 additions & 0 deletions app/src/main/resources/static/js/pwa/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const CACHE_NAME = 'trainer-advisor-v1';
const OFFLINE_URL = '/offline.html';

const urlsToCache = [
'/',
OFFLINE_URL,
'/styles/styles-qyoga.css',
'/vendor/bootstrap/css/bootstrap.css',
'/vendor/bootstrap/js/bootstrap.bundle.min.js',
'/img/icon.png',
'/img/icon-192x192.png',
'/img/icon-512x512.png',
'/manifest.json'
];

// Install service worker and cache assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});

// Serve cached content when offline
self.addEventListener('fetch', event => {
// For all other requests, go to the cache first, and then the network.
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request.url);
if (cachedResponse) {
// Return the cached response if it's available.
return cachedResponse;
}
// If resource isn't in the cache, return a 404.
return new Response(null, {status: 404});
})(),
);
});

// Clean up old caches
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
24 changes: 24 additions & 0 deletions app/src/main/resources/static/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "Trainer Advisor",
"short_name": "TrainerAdvisor",
"description": "Приложение для управления терапевтическими программами",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#e6e6e6",
"theme_color": "#e6e6e6",
"icons": [
{
"src": "/img/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/img/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
46 changes: 46 additions & 0 deletions app/src/main/resources/static/offline.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Trainer Advisor - Оффлайн</title>
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>

<script defer src="/vendor/fontawesome-6.5.2/js/fontawesome.min.js"></script>
<link href="/vendor/fontawesome-6.5.2/css/all.min.css" rel="stylesheet">

<script src="/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<link href="/vendor/bootstrap/css/bootstrap.css" rel="stylesheet"/>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f8f9fc;
text-align: center;
padding: 20px;
}

.offline-container {
max-width: 500px;
padding: 2rem;
border-radius: 10px;
background: white;
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
}
</style>
</head>
<body>
<div class="offline-container">
<div class="offline-icon">
<i class="bi bi-wifi-off"></i>
</div>
<h1>Вы находитесь оффлайн</h1>
<p>К сожалению, эта страница недоступна без подключения к интернету. Пожалуйста, проверьте ваше интернет-соединение
и попробуйте снова.</p>
<a class="btn btn-primary" onclick="window.location.href='/';">Вернуться на главную</a>
</div>

</body>
</html>
10 changes: 10 additions & 0 deletions app/src/main/resources/static/styles/styles-qyoga.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,14 @@ input[type=number] {
.w-sm-auto {
width: auto !important;
}
}

/* "мобилизация" табов в карточке клиента */
.nav-tabs .nav-link.active {
border: 0;
border-bottom: 2px solid #20c997 !important;
}

.nav-tabs .nav-link:hover {
border-color: transparent;
}
1 change: 1 addition & 0 deletions app/src/main/resources/templates/fragments/header.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
<meta content="#e6e6e6" name="theme-color"/>

<script defer src="/vendor/fontawesome-6.5.2/js/fontawesome.min.js"></script>
<link href="/vendor/fontawesome-6.5.2/css/all.min.css" rel="stylesheet">
Expand Down
12 changes: 8 additions & 4 deletions app/src/main/resources/templates/fragments/headerNavPanel.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<div th:fragment="headerNavPanel">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<div th:fragment="headerNavPanel(pageName)">
<nav class="sb-topnav navbar navbar-expand navbar bg-white d-flex align-items-baseline pe-4">
<button class="btn float-end" id="sidebarToggle" role="button">
<i class="fas fa-bars text-white" data-bs-target="#offcanvas"></i>
<i class="fas fa-bars text-black" data-bs-target="#offcanvas"></i>
</button>

<a class="navbar-brand ps-3">Trainer Advisor</a>
<a class="navbar-brand ps-3 text-truncate" th:text="${pageName}"></a>

<div class="ms-auto" th:if="${controlsView != null}">
<span th:replace="${controlsView}"></span>
</div>
</nav>
</div>
4 changes: 2 additions & 2 deletions app/src/main/resources/templates/fragments/leftNavPanel.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div id="layoutSidenav_nav" th:fragment="leftNavPanel (activeLink)">
<nav class="sb-sidenav accordion sb-sidenav-light d-flex flex-column flex-shrink-0 ps-3 pe-3 bg-body-tertiary"
<nav class="sb-sidenav accordion sb-sidenav-light d-flex flex-column flex-shrink-0 ps-3 pe-3 bg-white"
id="sidenavAccordion">

<ul class="nav nav-pills flex-column mb-auto mt-3">
<ul class="nav nav-pills flex-column mb-auto">

<li>
<div class="navbar-text bold">
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/resources/templates/public/landing.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
<meta content="#e6e6e6" name="theme-color"/>
<meta content="yes" name="mobile-web-app-capable">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="default" name="apple-mobile-web-app-status-bar-style">
<meta content="TrainerAdvisor" name="apple-mobile-web-app-title">

<link href="/img/icon.png" rel="icon" type="image/x-icon">

<link href="/img/icon-192x192.png" rel="icon" sizes="192x192" type="image/png">
<link href="/img/icon-512x512.png" rel="icon" sizes="512x512" type="image/png">
<link href="/img/icon-192x192.png" rel="apple-touch-icon">
<link href="/manifest.json" rel="manifest">
<link href="/vendor/bootstrap/css/bootstrap.css" rel="stylesheet"/>
<script src="/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script defer src="/js/pwa/pwa-register.js"></script>

<title>Trainer Advisor</title>
</head>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<body class="sb-nav-fixed">

<div th:replace="~{fragments/headerNavPanel :: headerNavPanel}"></div>
<div th:replace="~{fragments/headerNavPanel :: headerNavPanel(pageName = 'Настройки')}"></div>

<div id="layoutSidenav">

Expand All @@ -17,8 +17,6 @@
<main>
<div class="container px-4">

<view-title th:replace="~{components/view-title.html :: viewTitle(title='Настройки')}">
</view-title>
<div hx-get="/therapist/survey-forms/settings" hx-trigger="load" id="surveyFormsSettings"></div>
</div>
</main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
</head>
<body class="sb-nav-fixed">

<div th:replace="~{fragments/headerNavPanel :: headerNavPanel}"></div>
<div th:replace="~{fragments/headerNavPanel :: headerNavPanel(pageName = ${pageMode == 'CREATE' ? 'Новый приём' : appointment.clientTitle }, controlsView = ~{ :: #deleteAppointmentLink})}">
<a class="btn btn-outline-danger text mb-1"
id="deleteAppointmentLink"
th:hx-confirm="'Удалить приём для ' + ${appointment.clientTitle} + '?'"
th:hx-delete="@{/therapist/appointments/{id}?returnTo={returnTo} (id=${appointmentId},returnTo=${appointment.dateTime.toLocalDate()})}"
th:if="${pageMode == 'EDIT'}">
<i class="fas fa-trash"></i>
</a>
</div>

<div id="layoutSidenav">

Expand All @@ -18,21 +26,8 @@
<div id="layoutSidenav_content">
<main>
<section class="container px-4">
<h1 class="mt-4 pb-2 mb-3 border-bottom d-flex justify-content-between align-items-baseline">
<span th:text="${pageMode == 'CREATE' ? 'Новый приём' : appointment.clientTitle }">
Новый приём
</span>

<a class="btn btn-outline-danger text"
id="deleteAppointmentLink"
th:hx-confirm="'Удалить приём для ' + ${appointment.clientTitle} + '?'"
th:hx-delete="@{/therapist/appointments/{id}?returnTo={returnTo} (id=${appointmentId},returnTo=${appointment.dateTime.toLocalDate()})}"
th:if="${pageMode == 'EDIT'}">
<i class="fas fa-trash"></i>
</a>
</h1>

<form class="mt-4"

<form
hx-push-url="true" hx-swap="outerHTML" hx-target="body"
id="editAppointmentForm"
th:attr="hx-post=${pageMode == 'CREATE' ? '/therapist/appointments/new' : null},hx-put=${pageMode == 'EDIT' ? '/therapist/appointments/' + appointmentId : null}"
Expand Down
Loading
Loading