Skip to content

Commit 6de0d9d

Browse files
MaHaWolkeegan
andauthored
Incorporate client suggestions for feedback page (#222)
* adjust upper part of feedback page to proposed layout * reorganize layout and make legend collapseable again * add better styling of tabs and legend * add better colors, restyle legend and feedback page * change modal styling, add overall summary * add print button, correct some stylings * add scroll to bottom functionality * correct scrolling * make legend better * add background coloring * add report printing function * format report --------- Co-authored-by: Liam Keegan <liam@keegan.ch>
1 parent 08777e5 commit 6de0d9d

File tree

4 files changed

+216
-357
lines changed

4 files changed

+216
-357
lines changed

frontend/src/lib/components/ChildrenFeedback.svelte

+184-87
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ let milestoneGroups = $state(
4848
{} as Record<number, Record<number, MilestoneGroupPublic>>,
4949
);
5050
let sessionkeys = $state([] as number[]);
51-
let showHistory = $state(false);
51+
let showHistory = $state(true);
5252
let detailed = $state({}) as Record<number, any>;
5353
let summary = $state({}) as Record<number, any>;
5454
let answerSessions = $state({}) as Record<number, MilestoneAnswerSessionPublic>;
@@ -57,23 +57,29 @@ let showMoreInfo = $state(false);
5757
const intervalSize = 4;
5858
let currentSessionIndices = $state([0, intervalSize]);
5959
let relevant_sessionkeys = $state([] as number[]);
60-
const milestonePresentation = [
60+
let milestonePresentation = $state([
6161
{
6262
icon: CheckCircleSolid,
63-
color: "green",
6463
text: i18n.tr.milestone.recommendOk,
64+
short: i18n.tr.milestone.recommendOkShort,
65+
class: "text-feedback-0 w-16",
66+
showExplanation: false,
6567
},
6668
{
6769
icon: ExclamationCircleSolid,
68-
color: "orange",
6970
text: i18n.tr.milestone.recommendWatch,
71+
short: i18n.tr.milestone.recommendWatchShort,
72+
class: "text-feedback-1 w-16",
73+
showExplanation: false,
7074
},
7175
{
7276
icon: CloseCircleSolid,
73-
color: "red",
7477
text: i18n.tr.milestone.recommmendHelp,
78+
short: i18n.tr.milestone.recommendHelpShort,
79+
class: "text-feedback-2 w-16 ",
80+
showExplanation: false,
7581
},
76-
];
82+
]);
7783
const breadcrumbdata: any[] = [
7884
{
7985
label: currentChild.name,
@@ -204,6 +210,59 @@ async function loadNext() {
204210
await loadDetailedFeedback(relevant_sessionkeys);
205211
}
206212
213+
function generateReport(): string {
214+
let report = "";
215+
// add title
216+
report += `<h1>${i18n.tr.milestone.reportTitle}</h1>\n\n`;
217+
// add today's date
218+
report += `${i18n.tr.milestone.date}: ${new Date().toLocaleDateString()} \n\n`;
219+
220+
// add name and age of child in the beginning
221+
report += `${i18n.tr.milestone.child}: ${currentChild.name}\n`;
222+
report += `${i18n.tr.milestone.born}: ${currentChild.month}/${currentChild.year} \n\n`;
223+
224+
// iterate over all answersessions
225+
226+
for (let [aid, values] of Object.entries(summary)) {
227+
// aid : value
228+
229+
const min = Math.min(...(Object.values(values) as number[]));
230+
report += `<h2>${i18n.tr.milestone.timeperiod}: ${makeTitle(Number(aid))}</h2> \n`;
231+
report += `<strong>${i18n.tr.milestone.summaryScore}:</strong> ${min === 1 ? i18n.tr.milestone.recommendOk : min === 0 ? i18n.tr.milestone.recommendWatch : min === -1 ? i18n.tr.milestone.recommmendHelp : i18n.tr.milestone.notEnoughDataYet} \n\n`;
232+
233+
for (let [mid, score] of Object.entries(values)) {
234+
// mid : score
235+
report += `<h3> ${milestoneGroups[aid][Number(mid)].text[i18n.locale].title}</h3>`;
236+
report += ` ${score === 1 ? i18n.tr.milestone.recommendOkMs : score === 0 ? i18n.tr.milestone.recommendWatchMs : score === -1 ? i18n.tr.milestone.recommmendHelp : i18n.tr.milestone.notEnoughDataYet} \n\n`;
237+
238+
for (let [ms_id, ms_score] of Object.entries(detailed[aid][mid])) {
239+
// ms_id : ms_score
240+
report += ` <strong>${
241+
milestoneGroups[aid][Number(mid)].milestones.find((element: any) => {
242+
return element.id === Number(ms_id);
243+
}).text[i18n.locale].title
244+
}:</strong>`;
245+
report += ` ${ms_score === 1 ? i18n.tr.milestone.recommendOkShort : ms_score === 0 ? i18n.tr.milestone.recommendWatchShort : ms_score === -1 ? i18n.tr.milestone.recommendHelpShort : i18n.tr.milestone.notEnoughDataYet} \n`;
246+
}
247+
}
248+
249+
report += "\n";
250+
}
251+
252+
return report;
253+
}
254+
255+
function printReport() {
256+
const report = generateReport();
257+
const printWindow = window.open("", "", "height=600,width=800");
258+
if (printWindow === null) {
259+
return;
260+
}
261+
printWindow.document.write(`<pre>${report}</pre>`);
262+
printWindow.document.close();
263+
printWindow.print();
264+
}
265+
207266
function formatDate(date: string): string {
208267
const dateObj = new Date(date);
209268
return [
@@ -219,6 +278,13 @@ function makeTitle(aid: number): string {
219278
: formatDate(answerSessions[aid].created_at);
220279
}
221280
281+
function scrollToBottom() {
282+
window.scrollTo({
283+
top: document.body.scrollHeight * 0.35,
284+
behavior: "instant",
285+
});
286+
}
287+
222288
async function setup() {
223289
await loadAnswersessions();
224290
if (Object.keys(answerSessions).length === 0) {
@@ -231,73 +297,84 @@ async function setup() {
231297
let promise = $state(setup());
232298
</script>
233299

234-
{#snippet evaluation(aid: number, milestone_or_group: MilestonePublic | MilestoneGroupPublic | undefined, value: number, isMilestone: boolean, withText: boolean = false)}
235-
<div class="text-gray-700 dark:text-gray-400 space-x-2 space-y-4 p-2 m-2">
236-
{console.log(' aid: ', aid, milestone_or_group?.text[i18n.locale].title, value, isMilestone, withText)}
300+
{#snippet summaryEvaluation(aid: number)}
301+
<div class="flex flex-col md:flex-row items-center justify-center w-full m-2 p-2 text-gray-700 dark:text-gray-400">
302+
{#if Math.min(...(Object.values(summary[aid]) as number[])) === 1}
303+
<CheckCircleSolid size="xl" class="text-feedback-0 mr-2 pr-2"/>
304+
<span class="font-bold mx-2 px-2 items-center justify-center">{i18n.tr.milestone.summaryScore}</span>
305+
{i18n.tr.milestone.recommendOk}
306+
{:else if Math.min(...(Object.values(summary[aid]) as number[])) === 0}
307+
<BellActiveSolid size="xl" class="text-feedback-1 mr-2 pr-2"/>
308+
<span class="font-bold mx-2 px-2 items-center justify-center">{i18n.tr.milestone.summaryScore}</span>
309+
{i18n.tr.milestone.recommendWatch}
310+
{:else if Math.min(...(Object.values(summary[aid]) as number[])) === -1}
311+
<CloseCircleSolid size="xl" class="text-feedback-2 mr-2 pr-2"/>
312+
<span class="font-bold mx-2 px-2 items-center justify-center"> {i18n.tr.milestone.summaryScore}</span>
313+
{i18n.tr.milestone.recommmendHelp}
314+
{:else}
315+
<CloseCircleSolid size="xl" color = "gray" />
316+
<span class="font-bold mx-2 px-2 items-center justify-center">{i18n.tr.milestone.summaryScore}</span>
317+
{i18n.tr.milestone.notEnoughDataYet}
318+
{/if}
319+
</div>
320+
<Hr classHr="mx-2"/>
321+
322+
{/snippet}
323+
324+
325+
{#snippet evaluation( milestone_or_group: MilestonePublic | MilestoneGroupPublic | undefined, value: number, isMilestone: boolean,)}
326+
<div class={`rounded-lg space-x-2 space-y-4 p-2 m-2 flex flex-col ${(value === 0 || value === -1) && isMilestone=== true ? "bg-feedback-background-0" : ""}`}>
237327
{#if value === 1}
238-
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center">
239-
<CheckCircleSolid color = "green" size="xl"/>
240-
<span class = "text-gray-700 dark:text-gray-400 font-bold " >
328+
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center m-2 p-2">
329+
<CheckCircleSolid size="xl" class="text-feedback-0"/>
330+
<span class = {`font-bold ${isMilestone? "text-gray-700 dark:text-gray-400": ""}`} >
241331
{milestone_or_group?.text[i18n.locale].title}
242332
</span>
243333
<Hr class="mx-2"/>
244-
{#if withText}
245-
<p>{i18n.tr.milestone.recommendOk}</p>
246-
{/if}
247334
</div>
248335
{:else if value === 0}
249-
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center">
250-
<BellActiveSolid color = "orange" size="xl"/>
251-
<span class = "text-gray-700 dark:text-gray-400 font-bold " >
336+
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center m-2 p-2">
337+
<BellActiveSolid size="xl" class="text-feedback-1"/>
338+
<span class = {`font-bold ${isMilestone? "text-gray-700 dark:text-gray-400": ""}`} >
252339
{milestone_or_group?.text[i18n.locale].title}
253340
</span>
254341
<Hr class="mx-2"/>
255-
{#if withText}
256-
<p>{i18n.tr.milestone.recommendWatch}</p>
257-
{/if}
258342
</div>
259343
{#if isMilestone}
260344
<span class = "ml-auto mt-4">
261345
<Button id="b1" onclick={()=>{
262346
showHelp= true;
263347
}}>{i18n.tr.milestone.help}</Button>
264-
<Modal title={i18n.tr.milestone.help} bind:open={showHelp} dismissable={true}>
348+
<Modal class = "m-2 p-2" title={i18n.tr.milestone.help} bind:open={showHelp} dismissable={true}>
265349
{milestone_or_group?.text[i18n.locale].help}
266350
</Modal>
267351
</span>
268352
{/if}
269353
{:else if value === -1}
270-
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center">
271-
<CloseCircleSolid color = "red" size="xl"/>
272-
<span class = "text-gray-700 dark:text-gray-400 font-bold " >
354+
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center m-2 p-2">
355+
<CloseCircleSolid size="xl" class="text-feedback-2"/>
356+
<span class = {`font-bold ${isMilestone? "text-gray-700 dark:text-gray-400": ""}`} >
273357
{milestone_or_group?.text[i18n.locale].title}
274358
</span>
275359
<Hr class="mx-2"/>
276-
{#if withText}
277-
<p>{i18n.tr.milestone.recommmendHelp}</p>
278-
{/if}
279360
</div>
280361
{#if isMilestone}
281362
<span class = "ml-auto mt-4">
282363
<Button id="b1" onclick={()=>{
283364
showHelp= true;
284365
}}>{i18n.tr.milestone.help}</Button>
285-
286-
<Modal title={i18n.tr.milestone.help} bind:open={showHelp} dismissable={true}>
366+
<Modal class = "m-2 p-2" title={i18n.tr.milestone.help} bind:open={showHelp} dismissable={true}>
287367
{milestone_or_group?.text[i18n.locale].help}
288368
</Modal>
289369
</span>
290370
{/if}
291371
{:else }
292-
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center">
372+
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2 items-center m-2 p-2">
293373
<CloseCircleSolid color = "gray" size="xl"/>
294-
<span class = "text-gray-700 dark:text-gray-400 font-bold " >
374+
<span class = {`font-bold ${isMilestone? "text-gray-700 dark:text-gray-400": ""}`} >
295375
{milestone_or_group?.text[i18n.locale].title}
296376
</span>
297377
<Hr class="mx-2"/>
298-
{#if withText}
299-
<p>{i18n.tr.milestone.notEnoughDataYet}</p>
300-
{/if}
301378
</div>
302379

303380
{/if}
@@ -319,93 +396,113 @@ let promise = $state(setup());
319396
</div>
320397
{:then}
321398

322-
<Heading tag="h2" class = "text-gray-700 dark:text-gray-400 items-center p-2 m-2 pb-4">{i18n.tr.milestone.feedbackTitle} </Heading>
399+
<Heading tag="h2" class = "text-gray-700 dark:text-gray-400 items-center p-2 m-2 pb-4">{i18n.tr.milestone.feedbackTitle} </Heading>
323400

324-
<div class ="m-2 p-2 pb-4 ">
325-
<p class="m-2 p-2 pb-4 text-gray-700 dark:text-gray-400">{i18n.tr.milestone.feedbackExplanation}</p>
401+
<div class ="m-2 p-2 pb-4 ">
402+
<p class="m-2 p-2 pb-4 text-gray-700 dark:text-gray-400 font-medium text-sm md:text-md">{i18n.tr.milestone.feedbackExplanation}</p>
326403

327-
<Button class = "m-2 p-2 pb-4 mb-4 items-center justify-center md:w-1/4" onclick = {() => {
328-
showMoreInfo = true;
329-
}}>{i18n.tr.milestone.moreInfoOnEval}</Button>
404+
<Modal class = "m-2 p-2" classHeader="flex justify-between items-center p-4 md:p-5 rounded-t-lg text-gray-700 dark:text-gray-400" title={i18n.tr.milestone.info} bind:open={showMoreInfo} dismissable={true}>
405+
<p class ="text-gray-700 dark:text-gray-400 font-medium text-sm md:text-md">{i18n.tr.milestone.feedbackExplanationDetailed}</p>
406+
<p class ="text-gray-700 dark:text-gray-400 font-medium text-sm md:text-md">{i18n.tr.milestone.feedbackDetailsMilestoneGroup}</p>
407+
<p class ="text-gray-700 dark:text-gray-400 font-medium text-sm md:text-md" >{i18n.tr.milestone.feedbackDetailsMilestone}</p>
408+
</Modal>
330409

410+
<Accordion class="p-2 m-2 w-full">
411+
<AccordionItem >
412+
<span slot="header" class="text-gray-700 dark:text-gray-400">{i18n.tr.milestone.legend}</span>
413+
<div class="w-full flex flex-col md:flex-row items-center justify-start">
414+
{#each milestonePresentation as milestone}
415+
<div class="text-gray-700 dark:text-gray-400 flex flex-col md:flex-row font-medium text-sm md:text-md items-center justify-start m-2 p-2">
331416

332-
<Modal classHeader="flex justify-between items-center p-4 md:p-5 rounded-t-lg text-gray-700 dark:text-gray-400" title={i18n.tr.milestone.info} bind:open={showMoreInfo} dismissable={true}>
333-
<p class ="text-gray-700 dark:text-gray-400">{i18n.tr.milestone.feedbackDetailsMilestoneGroup}</p>
334-
<p class ="text-gray-700 dark:text-gray-400">{i18n.tr.milestone.feedbackDetailsMilestone}</p>
335-
</Modal>
417+
<svelte:component this={milestone.icon} size="xl" class={milestone.class} />
336418

337-
<Accordion>
338-
<AccordionItem>
339-
<span slot="header" class="text-gray-700 dark:text-gray-400 flex items-center justify-center">
340-
<span class="font-bold">
341-
{i18n.tr.milestone.legend}
342-
</span>
343-
</span>
344-
<div class="flex flex-col text-gray-700 dark:text-gray-400 items-start p-2 m-2 space-y-6 justify-center">
345-
{#each milestonePresentation as milestone}
346-
<div class="mx-2 px-2 w-full flex flex-row items-center">
347-
<svelte:component this={milestone.icon} color={milestone.color} size="xl" class="mx-2"/>
348-
<p>{milestone.text}</p>
419+
<span class="font-bold justify-center mr-auto pr-auto">{milestone.short}</span>
420+
421+
<Button class="m-2 p-2 md:w-24 justify-center" onclick={() => {milestone.showExplanation=true;}}>{i18n.tr.milestone.moreInfoOnLegend}</Button>
349422
</div>
350-
<Hr classHr="mx-2 px-2 items-end w-full"/>
423+
<Modal class = "m-2 p-2" classHeader="flex justify-between items-center p-4 md:p-5 rounded-t-lg text-gray-700 dark:text-gray-400" bind:open={milestone.showExplanation} dismissable={true} title={milestone.short}>
424+
{milestone.text}
425+
</Modal>
351426
{/each}
352-
</div>
353-
</AccordionItem>
354-
</Accordion>
427+
</div>
428+
</AccordionItem>
429+
</Accordion>
430+
431+
<div class="flex items-center justify-start w-full m-2 p-2">
432+
<Button class = "m-2 p-2 pb-4 mb-4 items-center justify-center md:w-1/6 w-5/6" onclick = {() => {
433+
showMoreInfo = true;
434+
}}>{i18n.tr.milestone.moreInfoOnEval}
435+
</Button>
436+
</div>
437+
</div>
355438

356-
<Checkbox class= "pb-4 m-2 p-2 text-gray-700 dark:text-gray-400" bind:checked={showHistory} >{i18n.tr.milestone.showHistory}</Checkbox>
357-
<Hr classHr= "mx-2"/>
358-
</div>
439+
<Hr classHr= "w-full mx-2"/>
440+
441+
<div class ="m-2 p-2 pb-4 ">
359442

360-
<div class="m-2 p-2 pb-4 w-full">
361-
<Tabs tabStyle="underline" class="items-center flex flex-wrap">
443+
<p class = "justify-center font-bold m-2 p-2 text-gray-700 dark:text-gray-400">{i18n.tr.milestone.selectFeedback}</p>
444+
445+
<Checkbox class= "pb-4 m-2 p-2 text-gray-700 dark:text-gray-400" bind:checked={showHistory} >{i18n.tr.milestone.showHistory}</Checkbox>
446+
447+
<Hr classHr= "mx-2"/>
448+
</div>
449+
450+
<Tabs defaultClass="m-2 p-2 pb-4 items-center flex flex-wrap justify-between w-full text-gray-700 dark:text-gray-400">
362451
{#if showHistory === true}
363452
<Button size="md" type="button" class="md:w-16 md:h-8" on:click={() => {
364453
promise = loadLast();
454+
scrollToBottom();
365455
}}><ArrowLeftOutline class="w-4 h-4" /></Button>
366456
{/if}
367-
<div class="flex flex-col md:flex-row">
457+
<div class="flex flex-col md:flex-row justify-between">
368458
{#if relevant_sessionkeys.length=== 0}
369459
<p class="m-2 p-2 pb-4 text-gray-700 dark:text-gray-400">{i18n.tr.milestone.noFeedback}</p>
370460
{:else}
371461
{#each relevant_sessionkeys as aid}
372462
{#if showHistory === true || aid === sessionkeys[0]}
373-
<TabItem defaultClass="font-bold text-gray-700 dark:text-gray-400 m-2 p-2" title={makeTitle(aid)} open={aid === relevant_sessionkeys[0] }>
374-
<Accordion class="p-2 m-2">
463+
<TabItem defaultClass="font-bold m-2 p-2" title={makeTitle(aid)} open={aid === relevant_sessionkeys[0] }>
464+
465+
{@render summaryEvaluation(aid)}
466+
467+
<Accordion class="p-2 m-2 grid grid-cols-1 md:grid-cols-3 gap-4">
375468
{#each Object.entries(summary[aid]) as [mid, score]}
376-
<AccordionItem >
377-
<span slot="header" class="text-gray-700 dark:text-gray-400 items-center flex justify-center space-x-2">
378-
{@render evaluation(aid, milestoneGroups[aid][Number(mid)], score as number, false, false)}
469+
<div class="flex flex-col">
470+
<AccordionItem activeClass="flex flex-col m-2 rounded-xl text-white dark:text-white bg-primary-700 dark:bg-primary-700 hover:bg-primary-600 dark:hover:bg-primary-600 items-center justify-between w-full font-medium text-left" inactiveClass="flex flex-col rounded-xl text-white dark:text-white bg-primary-800 dark:bg-primary-800 hover:bg-primary-700 dark:hover:bg-primary-700 items-center justify-between w-full font-medium text-left m-2">
471+
<span slot="header" class="items-center flex justify-center space-x-2">
472+
{@render evaluation(milestoneGroups[aid][Number(mid)], score as number, false)}
379473
</span>
380-
<div class="flex-row justify-between">
381-
{#each Object.entries(detailed[aid][mid]) as [ms_id, ms_score]}
382-
{@render evaluation(
383-
aid,
384-
milestoneGroups[aid][Number(mid)].milestones.find((element: any) =>
385-
{
386-
return element.id === Number(ms_id);
387-
}),
388-
Number(ms_score),
389-
true, true
390-
)}
391-
<Hr classHr="mx-2"/>
392-
{/each}
393-
</div>
474+
{#each Object.entries(detailed[aid][mid]) as [ms_id, ms_score]}
475+
{@render evaluation(
476+
milestoneGroups[aid][Number(mid)].milestones.find((element: any) =>
477+
{
478+
return element.id === Number(ms_id);
479+
}),
480+
Number(ms_score),
481+
true
482+
)}
483+
<Hr classHr="mx-2"/>
484+
{/each}
394485
</AccordionItem>
486+
</div>
395487
{/each}
396488
</Accordion>
397489
</TabItem>
398490
{/if}
399491
{/each}
400492
{/if}
401493
</div>
494+
402495
{#if showHistory === true}
403496
<Button size="md" type="button" class="md:w-16 md:h-8" on:click={() => {
404497
promise = loadNext();
498+
scrollToBottom();
405499
}}><ArrowRightOutline class="w-4 h-4" /></Button>
406500
{/if}
407501
</Tabs>
408-
</div>
502+
503+
<div class="flex items-center justify-start w-full m-2 p-2 mb-4 pb-4">
504+
<Button class="md:w-64 md:h-8 m-2 p-2" onclick={printReport}>{i18n.tr.milestone.printReport}</Button>
505+
</div>
409506
{:catch error}
410507
<AlertMessage
411508
message = {`${alertMessage} ${error}`}

0 commit comments

Comments
 (0)