-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial support for user questions
- backend - add models/questions with UserQuestion and UserQuestionText - add /user-questions endpoint to get list of questions - add /admin/user-questions endpoints to create, update and delete questions - admin frontend - add UserQuestions component to edit list of questions - only support text and select inputs for now - refactor admin frontend
- Loading branch information
Showing
19 changed files
with
591 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 7 additions & 11 deletions
18
...ts/Admin/DeleteMilestoneGroupModal.svelte → ...c/lib/components/Admin/DeleteModal.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,21 @@ | ||
<script lang="ts"> | ||
import { Button, Modal } from 'flowbite-svelte'; | ||
import { ExclamationCircleOutline } from 'flowbite-svelte-icons'; | ||
import { deleteMilestoneGroup } from '$lib/admin'; | ||
import { _ } from 'svelte-i18n'; | ||
export let open: boolean; | ||
export let groupId: number | null; | ||
export let onClick: () => void; | ||
</script> | ||
|
||
<Modal bind:open size="xs" autoclose> | ||
<div class="text-center"> | ||
<ExclamationCircleOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-200" /> | ||
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400"> | ||
Are you sure you want to delete this MilestoneGroup? | ||
{$_('admin.delete-are-you-sure')} | ||
</h3> | ||
<Button | ||
color="red" | ||
class="me-2" | ||
on:click={() => { | ||
deleteMilestoneGroup(groupId); | ||
}}>Yes, I'm sure</Button | ||
> | ||
<Button color="alternative">No, cancel</Button> | ||
<Button color="red" class="me-2" on:click={onClick}> | ||
{$_('admin.yes-sure')} | ||
</Button> | ||
<Button color="alternative">{$_('admin.no-cancel')}</Button> | ||
</div> | ||
</Modal> |
139 changes: 139 additions & 0 deletions
139
frontend/src/lib/components/Admin/EditUserQuestionModal.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
<script lang="ts"> | ||
import { | ||
Button, | ||
Badge, | ||
Card, | ||
InputAddon, | ||
Textarea, | ||
Input, | ||
Label, | ||
ButtonGroup, | ||
Modal, | ||
Select, | ||
type SelectOptionType | ||
} from 'flowbite-svelte'; | ||
import { lang_id, languages } from '$lib/stores/adminStore'; | ||
import { updateUserQuestion } from '$lib/admin'; | ||
import InputPreview from '$lib/components/Admin/InputPreview.svelte'; | ||
export let open: boolean = false; | ||
export let userQuestion: object | null = null; | ||
let preview_lang_id = '1'; | ||
let preview_answer = ''; | ||
const inputTypes: SelectOptionType<string>[] = [ | ||
{ value: 'text', name: 'text' }, | ||
{ value: 'select', name: 'select' } | ||
]; | ||
function updateOptionsJson() { | ||
const values = userQuestion.options.split(';'); | ||
for (const lid in $languages) { | ||
const items = userQuestion.text[lid].options.split(';'); | ||
userQuestion.text[lid].options_json = JSON.stringify( | ||
values.map(function (value, index) { | ||
return { value: value, name: items[index] }; | ||
}) | ||
); | ||
} | ||
} | ||
export async function saveChanges() { | ||
try { | ||
await updateUserQuestion(userQuestion); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} | ||
</script> | ||
|
||
<Modal title="Edit user question" bind:open autoclose size="xl"> | ||
{#if userQuestion} | ||
<div class="flex flex-row items-center"> | ||
<div class="mr-5 grow"> | ||
<div class="mb-5"> | ||
<Label class="mb-2">Question</Label> | ||
{#each Object.values(userQuestion.text) as text} | ||
<div class="mb-1"> | ||
<ButtonGroup class="w-full"> | ||
<InputAddon>{$languages[text.lang_id]}</InputAddon> | ||
<Input | ||
bind:value={text.question} | ||
on:input={() => { | ||
userQuestion = userQuestion; | ||
}} | ||
placeholder="Question" | ||
/> | ||
</ButtonGroup> | ||
</div> | ||
{/each} | ||
</div> | ||
<div class="mb-5"> | ||
<Label class="mb-2">Input type</Label> | ||
<Select class="mt-2" items={inputTypes} bind:value={userQuestion.input} placeholder="" /> | ||
</div> | ||
{#if userQuestion.input === 'select'} | ||
<div class="mb-5"> | ||
<Label class="mb-2">Options</Label> | ||
<div class="mb-1"> | ||
<ButtonGroup class="w-full"> | ||
<InputAddon>name</InputAddon> | ||
<Textarea | ||
bind:value={userQuestion.options} | ||
on:input={updateOptionsJson} | ||
placeholder="Option names" | ||
/> | ||
</ButtonGroup> | ||
</div> | ||
{#each Object.values(userQuestion.text) as text} | ||
<div class="mb-1"> | ||
<ButtonGroup class="w-full"> | ||
<InputAddon>{$languages[text.lang_id]}</InputAddon> | ||
<Textarea | ||
bind:value={text.options} | ||
on:input={updateOptionsJson} | ||
placeholder="Options" | ||
/> | ||
</ButtonGroup> | ||
</div> | ||
{/each} | ||
</div> | ||
{/if} | ||
</div> | ||
<div> | ||
<Card> | ||
<div class="mb-5"> | ||
<Label class="mb-2">Preview</Label> | ||
<div class="flex flex-row"> | ||
<ButtonGroup class="mb-2 mr-2"> | ||
{#each Object.entries($languages) as [lid, lang]} | ||
<Button | ||
checked={preview_lang_id === lid} | ||
on:click={(e) => { | ||
e.stopPropagation(); | ||
preview_lang_id = lid; | ||
}}>{lang}</Button | ||
> | ||
{/each} | ||
</ButtonGroup> | ||
</div> | ||
<Card class="mb-4 bg-blue-300"> | ||
<InputPreview | ||
data={userQuestion} | ||
lang_id={preview_lang_id} | ||
bind:answer={preview_answer} | ||
/> | ||
</Card> | ||
<Label class="mb-2">Generated answer:</Label> | ||
<Badge large border color="dark">{preview_answer}</Badge> | ||
</div> | ||
</Card> | ||
</div> | ||
</div> | ||
{/if} | ||
<svelte:fragment slot="footer"> | ||
<Button color="green" on:click={saveChanges}>Save changes</Button> | ||
<Button color="alternative">Cancel</Button> | ||
</svelte:fragment> | ||
</Modal> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<script lang="ts"> | ||
import { Input, Select, Label } from 'flowbite-svelte'; | ||
export let data; | ||
export let lang_id; | ||
export let answer = ''; | ||
function parse_options_json(options_json) { | ||
try { | ||
return JSON.parse(data.text[lang_id].options_json); | ||
} catch (e) { | ||
console.log("Couldn't parse options_json"); | ||
console.log(e); | ||
} | ||
return []; | ||
} | ||
$: items = parse_options_json(data.text[lang_id].options_json); | ||
</script> | ||
|
||
<div class="mb-5"> | ||
<Label class="font-semibold text-gray-700 dark:text-gray-400">{data.text[lang_id].question}</Label | ||
> | ||
</div> | ||
<div class="mb-5"> | ||
{#if data.input === 'select'} | ||
<Select {items} bind:value={answer} placeholder="" /> | ||
{:else} | ||
<Input type="text" bind:value={answer} /> | ||
{/if} | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.