Skip to content

Commit fb8d9ee

Browse files
authored
fix/qg-274: исправлена ошибка со сбросом данных формы записи журнала при поиске ТЗ (#275)
Судя по всему, проблема была вызвана использованием нативного datalist для реализации подсказок вариантов терапевтических задач. Заодно вся форма приведена к актуальному UI/UX-гайдлайну с плавающими лейблами и звёздочками обязательности. А поле ввода даты переведено на нативный дейтпикер, т.к. текстовое поле неудобно на мобилках и вообще не понятно почему сразу так не сделали
2 parents d21f42c + 78a32a0 commit fb8d9ee

File tree

21 files changed

+170
-95
lines changed

21 files changed

+170
-95
lines changed

app/src/main/kotlin/pro/azhidkov/platform/spring/sdj/AggregateReferenceDeserializer.kt

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@ package pro.azhidkov.platform.spring.sdj
33
import com.fasterxml.jackson.core.JsonParser
44
import com.fasterxml.jackson.databind.*
55
import com.fasterxml.jackson.databind.deser.ContextualDeserializer
6+
import com.fasterxml.jackson.databind.type.TypeFactory
67
import org.springframework.data.jdbc.core.mapping.AggregateReference
78
import pro.azhidkov.platform.spring.sdj.ergo.hydration.AggregateReferenceTarget
89
import pro.azhidkov.platform.spring.sdj.ergo.hydration.Identifiable
10+
import kotlin.reflect.full.memberProperties
11+
import kotlin.reflect.jvm.jvmErasure
912

1013

11-
class AggregateReferenceDeserializer : JsonDeserializer<AggregateReference<*, *>>(), ContextualDeserializer {
12-
13-
private var type: JavaType? = null
14+
class AggregateReferenceDeserializer(
15+
private var type: JavaType = TypeFactory.unknownType()
16+
) : JsonDeserializer<AggregateReference<*, *>>(), ContextualDeserializer {
1417

1518
override fun deserialize(parser: JsonParser, context: DeserializationContext): AggregateReference<*, *>? {
1619
val node = parser.codec.readTree<JsonNode>(parser)
17-
val propertyNames = node.properties().map { it.key }
18-
return if (propertyNames == setOf("id")) {
19-
AggregateReference.to<Any, Any>(node.get("id").numberValue())
20+
val propertyNames = node.properties().map { it.key }.toSet()
21+
return if (propertyNames == setOf(ID_FIELD_NAME)) {
22+
val idType = getIdType(type)
23+
AggregateReference.to<Any, Any>(context.readTreeAsValue(node.findValue(ID_FIELD_NAME), idType))
2024
} else if (propertyNames.size > 1) {
2125
AggregateReferenceTarget<Identifiable<Any>, Any>(context.readTreeAsValue(node, type))
2226
} else {
@@ -25,13 +29,19 @@ class AggregateReferenceDeserializer : JsonDeserializer<AggregateReference<*, *>
2529
}
2630

2731
override fun createContextual(ctx: DeserializationContext, property: BeanProperty): JsonDeserializer<*> {
28-
// Это странный костыль который помог мне быстро завести десериализацию ProgramExercise
29-
// Но подозреваю, в каком-то более сложном случае он взоравётся
30-
if (type == null) {
31-
this.type = property.type.containedType(0)
32-
}
33-
return this
32+
return AggregateReferenceDeserializer(property.type.containedType(0))
33+
}
3434

35+
companion object {
36+
const val ID_FIELD_NAME = "id"
3537
}
3638

37-
}
39+
}
40+
41+
private fun getIdType(type: JavaType): Class<out Any> =
42+
type.rawClass.kotlin
43+
.memberProperties
44+
.first { it.name == AggregateReferenceDeserializer.ID_FIELD_NAME }
45+
.returnType
46+
.jvmErasure
47+
.java

app/src/main/kotlin/pro/qyoga/app/therapist/clients/journal/edit_entry/create/CreateJournalEntryOp.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CreateJournalEntryOp(
3030
checkNotNull(client) { "Client for journal entry not found by id=$clientId" }
3131

3232
val therapeuticTask = therapeuticTasksRepo.getOrCreate(
33-
TherapeuticTask(principal.id, editJournalEntryRq.therapeuticTaskName)
33+
TherapeuticTask(principal.id, editJournalEntryRq.therapeuticTaskTitle)
3434
)
3535
val newEntry = JournalEntry(
3636
client.ref(),

app/src/main/kotlin/pro/qyoga/app/therapist/clients/journal/edit_entry/create/CreateJournalEntryPageModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ data class CreateJournalEntryPageModel(
2121
ModelAndView(
2222
viewId(JOURNAL_ENTRY_VIEW_NAME, fragment), mapOf(
2323
"client" to client,
24+
"entry" to null,
2425
"entryDate" to entryDate,
2526
"duplicatedDate" to duplicatedDate,
2627
"formAction" to createFormAction(client.ref())

app/src/main/kotlin/pro/qyoga/app/therapist/clients/journal/edit_entry/edit/EditJournalEntryOp.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class EditJournalEntryOp(
2626
principal: QyogaUserDetails,
2727
): JournalEntry? {
2828
val therapeuticTask = therapeuticTasksRepo.getOrCreate(
29-
TherapeuticTask(principal.id, editJournalEntryRq.therapeuticTaskName)
29+
TherapeuticTask(principal.id, editJournalEntryRq.therapeuticTaskTitle)
3030
)
3131

3232
val query = query {

app/src/main/kotlin/pro/qyoga/app/therapist/therapy/therapeutic_tasks/components/SearchTherapeuticTasksController.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class SearchTherapeuticTasksController(
1919

2020
@GetMapping
2121
fun search(
22-
@RequestParam("therapeuticTaskName") searchKey: String,
22+
@RequestParam(SEARCH_KEY_PARAM_NAME) searchKey: String,
2323
@AuthenticationPrincipal therapist: QyogaUserDetails,
2424
): ModelAndView {
2525
val tasks =
@@ -35,4 +35,8 @@ class SearchTherapeuticTasksController(
3535
)
3636
}
3737

38+
companion object {
39+
const val SEARCH_KEY_PARAM_NAME = "therapeuticTaskName"
40+
}
41+
3842
}

app/src/main/kotlin/pro/qyoga/core/clients/journals/dtos/EditJournalEntryRq.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
package pro.qyoga.core.clients.journals.dtos
22

3-
import com.fasterxml.jackson.annotation.JsonFormat
4-
import org.springframework.format.annotation.DateTimeFormat
53
import pro.azhidkov.platform.spring.sdj.ergo.hydration.resolveOrThrow
64
import pro.qyoga.core.clients.journals.model.JournalEntry
7-
import pro.qyoga.l10n.RUSSIAN_DATE_FORMAT_PATTERN
5+
import pro.qyoga.core.therapy.therapeutic_tasks.model.TherapeuticTaskRef
86
import java.time.LocalDate
97

108

119
data class EditJournalEntryRq(
12-
@DateTimeFormat(pattern = RUSSIAN_DATE_FORMAT_PATTERN)
13-
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = RUSSIAN_DATE_FORMAT_PATTERN)
1410
val date: LocalDate,
15-
val therapeuticTaskName: String,
11+
val therapeuticTask: TherapeuticTaskRef?,
12+
val therapeuticTaskTitle: String,
1613
val journalEntryText: String,
1714
val version: Long
1815
) {
1916

2017
constructor(journalEntry: JournalEntry) : this(
2118
journalEntry.date,
19+
journalEntry.therapeuticTask,
2220
journalEntry.therapeuticTask.resolveOrThrow().name,
2321
journalEntry.entryText,
2422
journalEntry.version

app/src/main/resources/templates/components/combo-box.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<div class="dropdown combo-box-div"
2-
th:fragment="comboBox(name, fetchUrl, value, valueName, placeholderView)"
2+
th:attr="${'@itemSelected'}=${itemSelected}"
3+
th:fragment="comboBox(name, fetchUrl, value, valueName, placeholderView, itemSelected, required, selectOnly)"
34
x-data="{showComboBox: false, searchKey: $el.querySelector('input[type=text]').value}">
45

5-
<input class="idInput"
6+
<input class="idInput form-control"
67
th:id="${name + 'Id'}"
78
th:name="${name}"
89
th:value="${value ?: ''}"
10+
th:required="${required}"
911
type="hidden">
1012

1113
<div class="form-floating">
@@ -84,7 +86,11 @@
8486
idInput.value = button.dataset.itemId;
8587
idInput.setCustomValidity('');
8688
titleInput.value = button.dataset.itemTitle;
89+
90+
idInput.setCustomValidity('');
8791
titleInput.setCustomValidity('');
92+
93+
titleInput.dispatchEvent(new CustomEvent('itemselected', { bubbles: true, detail: { itemId: idInput.value, itemTitle: titleInput.value} }));
8894
"
8995
th:data-item-id="${item.id}"
9096
th:data-item-title="${item.title}"

app/src/main/resources/templates/therapist/appointments/appointment-edit.html

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,16 @@ <h1 class="mt-4 pb-2 mb-3 border-bottom d-flex justify-content-between align-ite
5353

5454
<combo-box-fragment id="client"
5555
th:replace="~{components/combo-box.html :: comboBox(
56-
name='client', fetchUrl='/therapist/clients/autocomplete-search',
57-
value=${appointment?.client?.id}, valueName=${appointment?.clientTitle},
58-
placeholderView=~{:: #clientPlaceholder}, minlength=2, maxlength=100, required=true)}">
56+
name='client',
57+
fetchUrl='/therapist/clients/autocomplete-search',
58+
value=${appointment?.client?.id},
59+
valueName=${appointment?.clientTitle},
60+
placeholderView=~{:: #clientPlaceholder},
61+
itemSelected='',
62+
minlength=2,
63+
maxlength=100,
64+
required=true,
65+
selectOnly=true)}">
5966
<label id="clientPlaceholder">Клиент <span class="text-danger">*</span></label>
6067
</combo-box-fragment>
6168
<div class="form-text">Начните вводить имя или телефон для поиска</div>
@@ -65,9 +72,16 @@ <h1 class="mt-4 pb-2 mb-3 border-bottom d-flex justify-content-between align-ite
6572
<div class="col-12 mb-3 mb-sm-0 col-sm-4">
6673
<combo-box-fragment id="appointmentType"
6774
th:replace="~{components/combo-box.html :: comboBox(
68-
name='appointmentType', fetchUrl='/therapist/appointments/types/autocomplete-search',
69-
value=${appointment?.appointmentType?.id}, valueName=${appointment?.appointmentTypeTitle},
70-
placeholderView=~{:: #typePlaceholder}, minlength=2, maxlength=50, required=true, selectOnly = false)}">
75+
name='appointmentType',
76+
fetchUrl='/therapist/appointments/types/autocomplete-search',
77+
value=${appointment?.appointmentType?.id},
78+
valueName=${appointment?.appointmentTypeTitle},
79+
placeholderView=~{:: #typePlaceholder},
80+
itemSelected='',
81+
minlength=2,
82+
maxlength=50,
83+
required=true,
84+
selectOnly=false)}">
7185
<label id="typePlaceholder">Тип приёма <span class="text-danger">*</span></label>
7286
</combo-box-fragment>
7387
<div class="form-text">Начните вводить название для поиска</div>
@@ -77,9 +91,16 @@ <h1 class="mt-4 pb-2 mb-3 border-bottom d-flex justify-content-between align-ite
7791
<div class="col-12 col-sm-4">
7892
<combo-box-fragment id="therapeuticTask"
7993
th:replace="~{components/combo-box.html :: comboBox(
80-
name='therapeuticTask', fetchUrl='/therapist/therapeutic-tasks/autocomplete-search-combo-box',
81-
value=${appointment?.therapeuticTask?.id}, valueName=${appointment?.therapeuticTaskTitle},
82-
placeholderView=~{:: #therapeuticTaskPlaceholder}, minlength=2, maxlength=50)}">
94+
name='therapeuticTask',
95+
fetchUrl='/therapist/therapeutic-tasks/autocomplete-search-combo-box',
96+
value=${appointment?.therapeuticTask?.id},
97+
valueName=${appointment?.therapeuticTaskTitle},
98+
placeholderView=~{:: #therapeuticTaskPlaceholder},
99+
itemSelected='',
100+
minlength=2,
101+
maxlength=50,
102+
required=false,
103+
selectOnly=true)}">
83104
<label id="therapeuticTaskPlaceholder">Терапевтическая задача</label>
84105
</combo-box-fragment>
85106
<div class="form-text">Начните вводить название для поиска</div>
@@ -128,9 +149,17 @@ <h1 class="mt-4 pb-2 mb-3 border-bottom d-flex justify-content-between align-ite
128149
<div class="col-12 col-sm-3 mb-3 mb-sm-0">
129150
<combo-box-fragment id="timeZone"
130151
th:replace="~{components/combo-box.html :: comboBox(
131-
name='timeZone', fetchUrl='/components/time-zone/autocomplete-search',
132-
value=${appointment?.timeZone}, valueName=${appointment?.timeZoneTitle},
133-
placeholderView=~{:: #timeZonePlaceholder}, required=true, minlength=2, maxlength=50)}">
152+
name='timeZone',
153+
fetchUrl='/components/time-zone/autocomplete-search',
154+
value=${appointment?.timeZone},
155+
valueName=${appointment?.timeZoneTitle},
156+
placeholderView=~{:: #timeZonePlaceholder},
157+
itemSelected='',
158+
required=true,
159+
minlength=2,
160+
maxlength=50,
161+
required=true,
162+
selectOnly=true)}">
134163
<label id="timeZonePlaceholder">Часововй пояс <span
135164
class="text-danger">*</span></label>
136165
</combo-box-fragment>

0 commit comments

Comments
 (0)