Skip to content

Commit 826d27d

Browse files
Merge pull request #2004 from betagouv/feat/conv-lot4#1951
feat: Conv - Lot 4 Panneau d'affichage des brouillons de recommandation
2 parents fecd449 + 28abe62 commit 826d27d

21 files changed

Lines changed: 468 additions & 78 deletions

recoco/apps/conversations/templates/conversations/messages/fragments/conversation_topic.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@
5353
<span x-text="`${countOf.contacts} contact${countOf.contacts > 1 ? 's' : ''}`"></span>
5454
</p>
5555
</template>
56+
<template x-if="isSwitchtender && countOf.draft_recommendations > 0">
57+
<button type="button"
58+
class="topic-item__message-len topic-item__message-len--button fr-mt-0"
59+
@click="$store.sharedContentsPanel.open('draft-recommendations')">
60+
<span class="topic-item__shared-item">
61+
<span class="fr-icon-draft-line fr-icon--sm" aria-hidden="true"></span>
62+
<span x-text="`${countOf.draft_recommendations} brouillon${countOf.draft_recommendations > 1 ? 's' : ''}`"></span>
63+
</span>
64+
</button>
65+
</template>
5666
</div>
5767
</label>
5868
</div>

recoco/apps/conversations/templates/conversations/messages/fragments/conversations_new_feed.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
<!-- Date separator using simple Alpine.js approach -->
1212
<template x-if="shouldShowDate(element)">
1313
<p class="conversation-new__date"
14-
x-text="formatDateFrench(element.type == 'message' ? element.created : element.timestamp)"></p>
14+
x-text="formatDate(element.type == 'message' ? element.created : element.timestamp, { year: 'numeric', month: 'long', day: 'numeric', })">
15+
</p>
1516
</template>
1617
<template x-if="element.firstUnread">
1718
<div class="notification-limit" x-ref="scrollLine_general">
Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<div class="task-item__resource task-item__resource--bordered">
2-
<template x-if="!recommendation.visited">
3-
<span class="badge text-white task-item__resource-badge"
4-
x-text="!$store.djangoData.isAdvisor ? 'Nouveau' : 'Non lu par le demandeur' "></span>
5-
</template>
2+
{% if not draft_recommendation %}
3+
<template x-if="!recommendation.visited">
4+
<span class="badge text-white task-item__resource-badge"
5+
x-text="!$store.djangoData.isAdvisor ? 'Nouveau' : 'Non lu par le demandeur' "></span>
6+
</template>
7+
{% endif %}
68
<template x-if="recommendation.topic">
79
<div class="task-item__topic"
810
:style="{ '--color-topic': stringToColor(recommendation.topic?.name) }">
@@ -30,27 +32,29 @@ <h4>
3032
<p class="task-item__resource-subtitle fr-mt-1w fr-mb-0"
3133
x-text="recommendation.resource.subtitle"></p>
3234
</template>
33-
<div class="d-flex justify-content-between fr-mt-1w">
34-
<div class="d-flex">
35-
{% include "conversations/messages/fragments/tasks_status_buttons_js.html" %}
36-
<template x-if="recommendation.status !== TASK_STATUSES.DONE && recommendation.status !== TASK_STATUSES.NOT_INTERESTED && recommendation.status !== TASK_STATUSES.ALREADY_DONE && !{{ project.is_inactive_since|yesno:'true,false' }}">
37-
<div class="d-flex align-items-center active-reminder"
38-
x-tooltip="`Le demandeur recevra des rappels email tant que cette recommandation n'est pas traitée`">
39-
<span class="fr-icon-notification-on-line fr-icon--sm fr-mr-1v"
40-
aria-hidden="true"></span>
41-
<span>Rappels actifs</span>
42-
</div>
43-
</template>
44-
<template x-if="recommendation.status === TASK_STATUSES.DONE || recommendation.status === TASK_STATUSES.NOT_INTERESTED || recommendation.status === TASK_STATUSES.ALREADY_DONE || {{ project.is_inactive_since|yesno:'true,false' }}">
45-
<div class="d-flex align-items-center non-active-reminder"
46-
x-tooltip="`Pas de rappel envoyé au demandeur sur cette recommandation déjà traitée`">
47-
<span class="fr-icon-notification-off-line fr-icon--sm fr-mr-1v"
48-
aria-hidden="true"></span>
49-
<span>Pas de rappel</span>
50-
</div>
51-
</template>
35+
{% if not draft_recommendation %}
36+
<div class="d-flex justify-content-between fr-mt-1w">
37+
<div class="d-flex">
38+
{% include "conversations/messages/fragments/tasks_status_buttons_js.html" %}
39+
<template x-if="recommendation.status !== TASK_STATUSES.DONE && recommendation.status !== TASK_STATUSES.NOT_INTERESTED && recommendation.status !== TASK_STATUSES.ALREADY_DONE && !{{ project.is_inactive_since|yesno:'true,false' }}">
40+
<div class="d-flex align-items-center active-reminder"
41+
x-tooltip="`Le demandeur recevra des rappels email tant que cette recommandation n'est pas traitée`">
42+
<span class="fr-icon-notification-on-line fr-icon--sm fr-mr-1v"
43+
aria-hidden="true"></span>
44+
<span>Rappels actifs</span>
45+
</div>
46+
</template>
47+
<template x-if="recommendation.status === TASK_STATUSES.DONE || recommendation.status === TASK_STATUSES.NOT_INTERESTED || recommendation.status === TASK_STATUSES.ALREADY_DONE || {{ project.is_inactive_since|yesno:'true,false' }}">
48+
<div class="d-flex align-items-center non-active-reminder"
49+
x-tooltip="`Pas de rappel envoyé au demandeur sur cette recommandation déjà traitée`">
50+
<span class="fr-icon-notification-off-line fr-icon--sm fr-mr-1v"
51+
aria-hidden="true"></span>
52+
<span>Pas de rappel</span>
53+
</div>
54+
</template>
55+
</div>
56+
<button class="fr-btn fr-btn--sm fr-btn--primary"
57+
@click.prevent="await openResourcePreviewPanel(recommendation, element)">Consulter</button>
5258
</div>
53-
<button class="fr-btn fr-btn--sm fr-btn--primary"
54-
@click.prevent="await openResourcePreviewPanel(recommendation, element)">Consulter</button>
55-
</div>
59+
{% endif %}
5660
</div>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!-- Recommendation card in shared contents panel -->
2+
<!-- djlint:off -->
3+
<div x-data='{
4+
recommendation: null,
5+
element: null,
6+
async openResourcePreviewPanel(recommendation, element) {
7+
// Close the shared contents panel but mark for re-open when returning
8+
$store.sharedContentsPanel.closeForDetail();
9+
10+
// Open the resource preview panel
11+
if ($store.resourcePreviewPanel) {
12+
$store.resourcePreviewPanel.open(recommendation, element);
13+
}
14+
}
15+
}'
16+
x-effect="recommendation = reco; element = { id: recommendation.messageId, posted_by: recommendation.messagePostedBy }">
17+
<!-- djlint:on -->
18+
<!-- Container for the draft recommendation card -->
19+
<div class="shared-contents-draft-reco-card__container">
20+
<div class="shared-contents-draft-reco-card__header">
21+
<span class="fr-icon-draft-line fr-icon--sm" aria-hidden="true"></span>
22+
<span>Brouillon</span>
23+
</div>
24+
<!-- Inside container -->
25+
<div class="shared-contents-draft-reco-card__inside-container">
26+
<div class="shared-contents-draft-reco-card__content">
27+
<!-- Intitial comment from the user -->
28+
<p class="shared-contents-draft-reco-card__initial-comment fr-mb-2w"
29+
x-text="recommendation.content"></p>
30+
<!-- Recommendation card content -->
31+
{% include "conversations/messages/fragments/recommendation_card_content.html" with draft_recommendation=True %}
32+
<!-- User that posted the draft recommendation -->
33+
<div class="d-flex align-items-center shared-contents-draft-reco-card__user fr-mt-2w"
34+
x-data="{user: recommendation.created_by}">
35+
{% include "user/user_card_js.html" with class_name="font-size-14px" %}
36+
<!-- Date of the draft recommendation -->
37+
<p class="shared-contents-draft-reco-card__date fr-ml-1v fr-mb-0">
38+
le <span x-text="formatDate(recommendation.created_on, { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' })"></span>
39+
</p>
40+
</div>
41+
</div>
42+
</div>
43+
<div class="shared-contents-draft-reco-card__footer fr-pt-3v fr-pl-3v">
44+
<div class="d-flex gap-2">
45+
<a class="fr-btn fr-btn--sm fr-icon-pencil-line fr-btn--icon-left fr-btn--tertiary fr-btn--tertiary-white-background"
46+
:href="editTaskUrl(recommendation.id, '{{ request.path }}')">Editer</a>
47+
<button class="fr-btn fr-btn--sm fr-icon-delete-line fr-btn--icon-left fr-btn--tertiary fr-btn--tertiary-white-background"
48+
@click="$dispatch('open-delete-modal', recommendation)">Supprimer</button>
49+
</div>
50+
<button class="fr-btn fr-btn--sm fr-icon-send-plane-fill fr-btn--icon-left fr-btn--primary justify-content-end"
51+
@click="publishDraftRecommendation(recommendation)">Publier</button>
52+
</div>
53+
</div>
54+
</div>

recoco/apps/conversations/templates/conversations/messages/fragments/shared_contents_panel.html

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,22 @@ <h2 class="shared-contents-panel__title">Contenus partagés</h2>
4949
x-text="`(${$store.sharedContentsPanel.filesCount})`"></span>
5050
</label>
5151
</div>
52+
<template x-if="isSwitchtender">
53+
<div class="fr-segmented__element">
54+
<input type="radio"
55+
value="draft-recommendations"
56+
id="shared-contents-tab-draft-recommendations"
57+
name="shared-contents-tab"
58+
:checked="$store.sharedContentsPanel.activeTab === 'draft-recommendations'"
59+
:disabled="$store.sharedContentsPanel.draftRecommendationsCount === 0"
60+
@click="$store.sharedContentsPanel.switchTab('draft-recommendations')">
61+
<label class="fr-label" for="shared-contents-tab-draft-recommendations">
62+
Brouillons
63+
<span class="shared-contents-panel__item-count"
64+
x-text="`(${$store.sharedContentsPanel.draftRecommendationsCount})`"></span>
65+
</label>
66+
</div>
67+
</template>
5268
</div>
5369
</fieldset>
5470
</div>
@@ -70,9 +86,10 @@ <h2 class="shared-contents-panel__title">Contenus partagés</h2>
7086
:key="reco.id">
7187
<div class="fr-mb-3v">
7288
<!-- Date header with grouping -->
73-
<template x-if="index === 0 || formatDateFrench(reco.messageCreated) !== formatDateFrench($store.sharedContentsPanel.recommendations[index - 1]?.messageCreated)">
89+
<template x-if="index === 0 || formatDate(reco.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' }) !== formatDate(reco.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' })">
7490
<p class="shared-contents-panel__date-header"
75-
x-text="formatDateFrench(reco.messageCreated)"></p>
91+
x-text="formatDate(reco.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' })">
92+
</p>
7693
</template>
7794
{% include "conversations/messages/fragments/shared_contents_recommendation_card.html" %}
7895
</div>
@@ -99,9 +116,10 @@ <h2 class="shared-contents-panel__title">Contenus partagés</h2>
99116
:key="file.id">
100117
<div>
101118
<!-- Date header with grouping -->
102-
<template x-if="index === 0 || formatDateFrench(file.messageCreated) !== formatDateFrench($store.sharedContentsPanel.files[index - 1]?.messageCreated)">
119+
<template x-if="index === 0 || formatDate(file.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' }) !== formatDate($store.sharedContentsPanel.files[index - 1]?.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' })">
103120
<p class="shared-contents-panel__date-header"
104-
x-text="formatDateFrench(file.messageCreated)"></p>
121+
x-text="formatDate(file.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' })">
122+
</p>
105123
</template>
106124
{% include "conversations/messages/fragments/shared_contents_file_card.html" with external=False %}
107125
</div>
@@ -122,7 +140,35 @@ <h2 class="shared-contents-panel__title">Contenus partagés</h2>
122140
</template>
123141
</div>
124142
</template>
143+
<!-- Draft recommendations tab -->
144+
<template x-if="isSwitchtender && $store.sharedContentsPanel.activeTab === 'draft-recommendations'">
145+
<div>
146+
<template x-if="$store.sharedContentsPanel.draftRecommendationsCount === 0">
147+
<div class="shared-contents-panel__empty">
148+
<span class="fr-icon fr-icon-draft-line" aria-hidden="true"></span>
149+
<p>Aucun brouillon de recommandation dans cette conversation.</p>
150+
</div>
151+
</template>
152+
<template x-if="$store.sharedContentsPanel.draftRecommendationsCount > 0">
153+
<div>
154+
<template x-for="(reco, index) in $store.sharedContentsPanel.draftRecommendations"
155+
:key="reco.id">
156+
<div class="fr-mb-3v">
157+
<!-- Date header with grouping -->
158+
<template x-if="index === 0 || formatDate(reco.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' }) !== formatDate($store.sharedContentsPanel.draftRecommendations[index - 1]?.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' })">
159+
<p class="shared-contents-panel__date-header"
160+
x-text="formatDate(reco.messageCreated, { year: 'numeric', month: 'long', day: 'numeric' })">
161+
</p>
162+
</template>
163+
{% include "conversations/messages/fragments/shared_contents_draft_recommendation_card.html" %}
164+
</div>
165+
</template>
166+
</div>
167+
</template>
168+
</div>
169+
</template>
125170
</div>
126171
</div>
172+
{% include "projects/project/fragments/tasks_modal/delete_task_confirmation_modal.html" %}
127173
</div>
128174
</template>
Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
<!-- Recommendation card in shared contents panel -->
2+
<!-- djlint:off -->
23
<div x-data='{
3-
recommendation: null,
4-
element: null,
5-
async openResourcePreviewPanel(recommendation, element) {
6-
// Close the shared contents panel but mark for re-open when returning
7-
$store.sharedContentsPanel.closeForDetail();
4+
recommendation: null,
5+
element: null,
6+
async openResourcePreviewPanel(recommendation, element) {
7+
// Close the shared contents panel but mark for re-open when returning
8+
$store.sharedContentsPanel.closeForDetail();
89
9-
// Open the resource preview panel
10-
if ($store.resourcePreviewPanel) {
11-
$store.resourcePreviewPanel.open(recommendation, element);
10+
// Open the resource preview panel
11+
if ($store.resourcePreviewPanel) {
12+
$store.resourcePreviewPanel.open(recommendation, element);
13+
}
1214
}
13-
}
14-
}'
15-
x-effect="recommendation = $store.sharedContentsPanel.recommendations.find(r => r.id === reco.id) || reco; element = { id: recommendation.messageId, posted_by: recommendation.messagePostedBy }">
15+
}'
16+
x-effect="recommendation = $store.sharedContentsPanel.recommendations.find(r => r.id === reco.id) || $store.sharedContentsPanel.draftRecommendations.find(r => r.id === reco.id) || reco; element = { id: recommendation.messageId, posted_by: recommendation.messagePostedBy }">
17+
<!-- djlint:on -->
1618
{% include "conversations/messages/fragments/recommendation_card_content.html" %}
1719
</div>

recoco/apps/tasks/serializers.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from recoco.rest_api.serializers import BaseSerializerMixin
2222

23+
from ..conversations.serializers import MessageSerializer
2324
from .models import Task, TaskFollowup
2425

2526

@@ -186,6 +187,43 @@ def validate_resource_id(self, value: int) -> int:
186187
return value
187188

188189

190+
class TaskWithMessageSerializer(TaskSerializer):
191+
class Meta:
192+
model = Task
193+
fields = [
194+
"id",
195+
"status",
196+
"visited",
197+
"public",
198+
"priority",
199+
"order",
200+
"intent",
201+
"content",
202+
"contact",
203+
"contact_id",
204+
"created_on",
205+
"updated_on",
206+
"created_by",
207+
"document",
208+
"resource",
209+
"resource_id",
210+
"topic",
211+
"ds_folder",
212+
"notifications",
213+
"followups_count",
214+
"comments_count",
215+
"site",
216+
"message",
217+
]
218+
read_only_fields = [
219+
"created_on",
220+
"updated_on",
221+
"created_by",
222+
]
223+
224+
message = MessageSerializer(read_only=True)
225+
226+
189227
class TaskNotificationSerializer(serializers.HyperlinkedModelSerializer):
190228
class Meta:
191229
model = notifications_models.Notification

recoco/apps/tasks/templates/tasks/tasks/task_create.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ <h2 class="push-reco__main-title">Créer une recommandation</h2>
2929
{% include "projects/project/fragments/action_pusher/type.html" %}
3030
{% include "projects/project/fragments/action_pusher/resource_search.html" %}
3131
{% include "projects/project/fragments/action_pusher/details_form.html" with form=type_form %}
32-
<input type="hidden"
33-
name="next"
34-
:value="draft ? '' : '{{ type_form.next.value }}'">
32+
<input type="hidden" name="next" :value="'{{ type_form.next.value }}'">
3533
<input type="hidden" name="public" x-model="public">
3634
</form>
3735
</div>

0 commit comments

Comments
 (0)