mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
Merge branch 'feature/exam-attempt-domain' of github.com:Tria-plc/emaui into feature/exam-attempt-domain
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ActionIcon, Alert, Button, Checkbox, Group, Loader, Stack, Text } from '@mantine/core';
|
import { ActionIcon, Alert, Button, Checkbox, Group, Loader, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IconGripVertical, IconInfoCircle, IconPlus, IconTrash } from '@tabler/icons-react';
|
import { IconGripVertical, IconInfoCircle, IconPlus, IconTrash } from '@tabler/icons-react';
|
||||||
import { BilingualInput, notify, useErrorHandler } from '@ema-platform/ui';
|
import { notify, useErrorHandler } from '@ema-platform/ui';
|
||||||
import type { BilingualValue } from '@ema-platform/ui';
|
import type { BilingualValue } from '@ema-platform/ui';
|
||||||
import {
|
import {
|
||||||
useGetQuestionWithOptionsQuery,
|
useGetQuestionWithOptionsQuery,
|
||||||
@@ -48,8 +48,10 @@ export function QuestionOptionsEditor({ questionId }: { questionId: string }) {
|
|||||||
// pretending to know what they were.
|
// pretending to know what they were.
|
||||||
}, [question]);
|
}, [question]);
|
||||||
|
|
||||||
const updateText = (index: number, text: BilingualValue) => {
|
const updateField = (index: number, lang: keyof BilingualValue, value: string) => {
|
||||||
setDraft((prev) => prev.map((o, i) => (i === index ? { ...o, text } : o)));
|
setDraft((prev) =>
|
||||||
|
prev.map((o, i) => (i === index ? { ...o, text: { ...o.text, [lang]: value } } : o)),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleCorrect = (index: number) => {
|
const toggleCorrect = (index: number) => {
|
||||||
@@ -93,26 +95,33 @@ export function QuestionOptionsEditor({ questionId }: { questionId: string }) {
|
|||||||
{t('question.options.hint')}
|
{t('question.options.hint')}
|
||||||
</Alert>
|
</Alert>
|
||||||
{draft.map((option, index) => (
|
{draft.map((option, index) => (
|
||||||
<Group key={index} gap="xs" wrap="nowrap" align="flex-end">
|
<Group key={index} gap="xs" wrap="nowrap" align="center">
|
||||||
<IconGripVertical size={16} style={{ opacity: 0.4, marginBottom: 8 }} />
|
<IconGripVertical size={16} style={{ opacity: 0.4 }} />
|
||||||
<BilingualInput
|
<Stack gap={6} style={{ flex: 1 }}>
|
||||||
label={t('question.options.optionLabel', { number: index + 1 })}
|
<TextInput
|
||||||
value={option.text}
|
label={t('question.options.optionEn', { number: index + 1 })}
|
||||||
onChange={(v) => updateText(index, v)}
|
value={option.text.en}
|
||||||
size="sm"
|
onChange={(e) => updateField(index, 'en', e.currentTarget.value)}
|
||||||
style={{ flex: 1 }}
|
size="sm"
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={t('question.options.optionAm', { number: index + 1 })}
|
||||||
|
value={option.text.am}
|
||||||
|
onChange={(e) => updateField(index, 'am', e.currentTarget.value)}
|
||||||
|
size="sm"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t('question.options.correct')}
|
label={t('question.options.correct')}
|
||||||
checked={option.isCorrect}
|
checked={option.isCorrect}
|
||||||
onChange={() => toggleCorrect(index)}
|
onChange={() => toggleCorrect(index)}
|
||||||
mb={4}
|
|
||||||
/>
|
/>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mb={4}
|
|
||||||
disabled={draft.length <= 2}
|
disabled={draft.length <= 2}
|
||||||
onClick={() => removeOption(index)}
|
onClick={() => removeOption(index)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -745,6 +745,8 @@ export const am: Translations = {
|
|||||||
title: "የመልስ አማራጮች",
|
title: "የመልስ አማራጮች",
|
||||||
hint: "ትክክለኛውን አማራጭ ምረጥ/ምረጪ። ማስቀመጥ መላውን የአማራጭ ስብስብ ይተካል።",
|
hint: "ትክክለኛውን አማራጭ ምረጥ/ምረጪ። ማስቀመጥ መላውን የአማራጭ ስብስብ ይተካል።",
|
||||||
optionLabel: "አማራጭ {{number}}",
|
optionLabel: "አማራጭ {{number}}",
|
||||||
|
optionEn: "አማራጭ {{number}} (እንግሊዝኛ)",
|
||||||
|
optionAm: "አማራጭ {{number}} (አማርኛ)",
|
||||||
correct: "ትክክለኛ",
|
correct: "ትክክለኛ",
|
||||||
addOption: "አማራጭ ጨምር",
|
addOption: "አማራጭ ጨምር",
|
||||||
save: "አማራጮችን አስቀምጥ",
|
save: "አማራጮችን አስቀምጥ",
|
||||||
|
|||||||
@@ -747,6 +747,8 @@ export const en = {
|
|||||||
title: 'Answer Options',
|
title: 'Answer Options',
|
||||||
hint: 'Mark every correct option. Saving replaces the entire option set.',
|
hint: 'Mark every correct option. Saving replaces the entire option set.',
|
||||||
optionLabel: 'Option {{number}}',
|
optionLabel: 'Option {{number}}',
|
||||||
|
optionEn: 'Option {{number}} (English)',
|
||||||
|
optionAm: 'Option {{number}} (Amharic)',
|
||||||
correct: 'Correct',
|
correct: 'Correct',
|
||||||
addOption: 'Add option',
|
addOption: 'Add option',
|
||||||
save: 'Save options',
|
save: 'Save options',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
|
Tooltip,
|
||||||
TextInput,
|
TextInput,
|
||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
rem,
|
rem,
|
||||||
@@ -37,6 +38,9 @@ export function BilingualInput({
|
|||||||
...rest
|
...rest
|
||||||
}: BilingualInputProps) {
|
}: BilingualInputProps) {
|
||||||
const [lang, setLang] = useState<'en' | 'am'>('en');
|
const [lang, setLang] = useState<'en' | 'am'>('en');
|
||||||
|
const otherLang = lang === 'en' ? 'am' : 'en';
|
||||||
|
const otherLangName = otherLang === 'en' ? 'English' : 'Amharic';
|
||||||
|
const otherIsEmpty = !value[otherLang]?.trim();
|
||||||
|
|
||||||
const toggle = () => setLang((l) => (l === 'en' ? 'am' : 'en'));
|
const toggle = () => setLang((l) => (l === 'en' ? 'am' : 'en'));
|
||||||
|
|
||||||
@@ -48,37 +52,59 @@ export function BilingualInput({
|
|||||||
value={value[lang]}
|
value={value[lang]}
|
||||||
onChange={(e) => onChange({ ...value, [lang]: e.currentTarget.value })}
|
onChange={(e) => onChange({ ...value, [lang]: e.currentTarget.value })}
|
||||||
rightSection={
|
rightSection={
|
||||||
<UnstyledButton
|
<Tooltip
|
||||||
onClick={toggle}
|
label={`Switch to ${otherLangName}${otherIsEmpty ? ' — empty' : ''}`}
|
||||||
aria-label={`Switch to ${lang === 'en' ? 'Amharic' : 'English'}`}
|
withArrow
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: rem(28),
|
|
||||||
height: rem(20),
|
|
||||||
borderRadius: rem(4),
|
|
||||||
fontSize: rem(10),
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: '0.05em',
|
|
||||||
background:
|
|
||||||
lang === 'en'
|
|
||||||
? 'var(--mantine-color-blue-1)'
|
|
||||||
: 'var(--mantine-color-teal-1)',
|
|
||||||
color:
|
|
||||||
lang === 'en'
|
|
||||||
? 'var(--mantine-color-blue-7)'
|
|
||||||
: 'var(--mantine-color-teal-7)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'background 150ms ease',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{lang === 'en' ? 'EN' : 'AM'}
|
<UnstyledButton
|
||||||
</UnstyledButton>
|
onClick={toggle}
|
||||||
|
aria-label={`Switch to ${otherLangName}`}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: rem(32),
|
||||||
|
height: rem(24),
|
||||||
|
borderRadius: rem(4),
|
||||||
|
fontSize: rem(11),
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.05em',
|
||||||
|
background:
|
||||||
|
lang === 'en'
|
||||||
|
? 'var(--mantine-color-blue-1)'
|
||||||
|
: 'var(--mantine-color-teal-1)',
|
||||||
|
color:
|
||||||
|
lang === 'en'
|
||||||
|
? 'var(--mantine-color-blue-7)'
|
||||||
|
: 'var(--mantine-color-teal-7)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background 150ms ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{lang === 'en' ? 'EN' : 'AM'}
|
||||||
|
{otherIsEmpty && (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
title={`${otherLangName} text is missing`}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: -2,
|
||||||
|
right: -2,
|
||||||
|
width: rem(7),
|
||||||
|
height: rem(7),
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'var(--mantine-color-red-6)',
|
||||||
|
border: '1px solid var(--mantine-color-body)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</UnstyledButton>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
styles={{
|
styles={{
|
||||||
input: {
|
input: {
|
||||||
paddingRight: rem(42),
|
paddingRight: rem(46),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|||||||
Reference in New Issue
Block a user