diff --git a/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt b/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt index a695604b..0f839be8 100644 --- a/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt +++ b/app/src/main/kotlin/pro/qyoga/app/infra/WebSecurityConfig.kt @@ -53,6 +53,8 @@ class WebSecurityConfig( .requestMatchers( HttpMethod.GET, "/", + "/offline.html", + "/manifest.json", "/register", "/components/**", "/styles/**", diff --git a/app/src/main/kotlin/pro/qyoga/app/publc/surverys/ProcessSurveyOp.kt b/app/src/main/kotlin/pro/qyoga/app/publc/surverys/ProcessSurveyOp.kt index c5c60b1b..fd18d194 100644 --- a/app/src/main/kotlin/pro/qyoga/app/publc/surverys/ProcessSurveyOp.kt +++ b/app/src/main/kotlin/pro/qyoga/app/publc/surverys/ProcessSurveyOp.kt @@ -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 @@ -16,8 +18,11 @@ 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() @@ -25,6 +30,12 @@ class ProcessSurveyOp( 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)) diff --git a/app/src/main/kotlin/pro/qyoga/core/clients/cards/model/Client.kt b/app/src/main/kotlin/pro/qyoga/core/clients/cards/model/Client.kt index 68ca39b2..837d1439 100644 --- a/app/src/main/kotlin/pro/qyoga/core/clients/cards/model/Client.kt +++ b/app/src/main/kotlin/pro/qyoga/core/clients/cards/model/Client.kt @@ -63,4 +63,7 @@ private fun prependTextBlock(value: String?, base: String?): String? { } return value.trimEnd('\n') + "\n\n" + base -} \ No newline at end of file +} + +fun Client.toLogString() = + "$lastName $firstName ($id)" \ No newline at end of file diff --git a/app/src/main/resources/static/img/icon-192x192.png b/app/src/main/resources/static/img/icon-192x192.png new file mode 100644 index 00000000..4368fc83 Binary files /dev/null and b/app/src/main/resources/static/img/icon-192x192.png differ diff --git a/app/src/main/resources/static/img/icon-512x512.png b/app/src/main/resources/static/img/icon-512x512.png new file mode 100644 index 00000000..8aaae017 Binary files /dev/null and b/app/src/main/resources/static/img/icon-512x512.png differ diff --git a/app/src/main/resources/static/js/pwa/pwa-register.js b/app/src/main/resources/static/js/pwa/pwa-register.js new file mode 100644 index 00000000..bbad9b65 --- /dev/null +++ b/app/src/main/resources/static/js/pwa/pwa-register.js @@ -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."); +} diff --git a/app/src/main/resources/static/js/pwa/sw.js b/app/src/main/resources/static/js/pwa/sw.js new file mode 100644 index 00000000..b17c0bb6 --- /dev/null +++ b/app/src/main/resources/static/js/pwa/sw.js @@ -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); + } + }) + ); + }) + ); +}); diff --git a/app/src/main/resources/static/manifest.json b/app/src/main/resources/static/manifest.json new file mode 100644 index 00000000..fae859bd --- /dev/null +++ b/app/src/main/resources/static/manifest.json @@ -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" + } + ] +} diff --git a/app/src/main/resources/static/offline.html b/app/src/main/resources/static/offline.html new file mode 100644 index 00000000..74ae8a66 --- /dev/null +++ b/app/src/main/resources/static/offline.html @@ -0,0 +1,46 @@ + + +
+ + +К сожалению, эта страница недоступна без подключения к интернету. Пожалуйста, проверьте ваше интернет-соединение + и попробуйте снова.
+ Вернуться на главную +