Enhance audience management features with new components and API integrations
- Added `AssignCoursesModal`, `AudienceMemberRow`, `AudienceTable`, and related components for improved audience management UI. - Implemented audience invite actions including resend and revoke functionality. - Updated audience API to support importing members and assigning courses. - Refactored audience data handling to include status and invite management. - Improved overall user experience with pagination and selection features in the audience table.
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { getExerciseQuestionLabel, type ExerciseQuestionRendererProps } from '@cio/question-types';
|
||||
import OptionImage from '../option-image.svelte';
|
||||
import type { ExerciseQuestionRendererProps } from '@cio/question-types';
|
||||
import { getOptionImageUrl, hasOptionImages } from '../option-image-utils';
|
||||
import { cn } from '../../../../tools';
|
||||
import McqPreviewOptionRow from '../shared/mcq-preview-option-row.svelte';
|
||||
|
||||
let { question, labels }: ExerciseQuestionRendererProps = $props();
|
||||
const label = (key: Parameters<typeof getExerciseQuestionLabel>[1], fallback = '') =>
|
||||
getExerciseQuestionLabel(labels, key, fallback);
|
||||
|
||||
const optionsHaveImages = $derived(hasOptionImages(question.options));
|
||||
</script>
|
||||
@@ -14,22 +11,7 @@
|
||||
<div class="ui:space-y-2">
|
||||
<ul class={optionsHaveImages ? 'ui:grid ui:grid-cols-2 ui:gap-3' : 'ui:space-y-1'}>
|
||||
{#each question.options ?? [] as option, index}
|
||||
{@const optionImageUrl = getOptionImageUrl(option)}
|
||||
<li class={cn(optionsHaveImages && 'ui:space-y-2 ui:rounded-md ui:p-2', 'ui:text-sm')}>
|
||||
<OptionImage
|
||||
src={optionImageUrl}
|
||||
alt={label('question.edit.image_alt')}
|
||||
variant="preview"
|
||||
hasAnyImageInOptions={optionsHaveImages}
|
||||
/>
|
||||
|
||||
<div>
|
||||
{option.label || option.value || [label('common.option_prefix'), String(index + 1)].filter(Boolean).join(' ')}
|
||||
{#if option.isCorrect}
|
||||
<span class="ui:ml-2 ui:text-xs ui:text-green-600">{label('common.correct_badge')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
<McqPreviewOptionRow {option} {index} optionImageUrl={getOptionImageUrl(option)} {optionsHaveImages} {labels} />
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { getExerciseQuestionLabel, type ExerciseQuestionRendererProps } from '@cio/question-types';
|
||||
import { Checkbox } from '../../../../base/checkbox';
|
||||
import { Label } from '../../../../base/label';
|
||||
import OptionImage from '../option-image.svelte';
|
||||
import { getOptionImageUrl, hasOptionImages } from '../option-image-utils';
|
||||
import { getMcqReviewRowBorderClass, getOptionNumericId } from '../option-review-utils';
|
||||
import OptionCorrectnessBadges from '../shared/option-correctness-badges.svelte';
|
||||
import { cn } from '../../../../tools';
|
||||
|
||||
let { question, answer = null, labels }: ExerciseQuestionRendererProps = $props();
|
||||
|
||||
const label = (key: Parameters<typeof getExerciseQuestionLabel>[1], fallback = '') =>
|
||||
getExerciseQuestionLabel(labels, key, fallback);
|
||||
|
||||
const selectedIds = $derived.by(() => {
|
||||
if (answer?.type !== 'CHECKBOX') return [] as number[];
|
||||
return answer.optionIds;
|
||||
});
|
||||
|
||||
const optionsHaveImages = $derived(hasOptionImages(question.options));
|
||||
</script>
|
||||
|
||||
<div class="ui:space-y-2">
|
||||
<div class={optionsHaveImages ? 'ui:grid ui:grid-cols-2 ui:gap-3' : 'ui:space-y-2'}>
|
||||
{#each question.options ?? [] as option, index}
|
||||
{@const optionId = `checkbox-review-${question.id ?? 'q'}-${index}`}
|
||||
{@const optionImageUrl = getOptionImageUrl(option)}
|
||||
{@const oid = getOptionNumericId(option)}
|
||||
{@const isSelected = oid !== null && selectedIds.includes(oid)}
|
||||
{@const isCorrect = option.isCorrect === true}
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
'ui:rounded-md ui:border ui:p-2',
|
||||
optionsHaveImages ? 'ui:space-y-2' : 'ui:flex ui:items-center ui:gap-2',
|
||||
getMcqReviewRowBorderClass(isSelected, isCorrect)
|
||||
)}
|
||||
>
|
||||
<OptionImage
|
||||
src={optionImageUrl}
|
||||
alt={label('question.edit.image_alt')}
|
||||
variant="take"
|
||||
hasAnyImageInOptions={optionsHaveImages}
|
||||
/>
|
||||
|
||||
<div class="ui:flex ui:items-center ui:gap-2">
|
||||
<Checkbox id={optionId} checked={isSelected} disabled={true} />
|
||||
<Label class="ui:cursor-default ui:font-normal ui:w-full" for={optionId}>
|
||||
{option.label ||
|
||||
option.value ||
|
||||
[label('common.option_prefix'), String(index + 1)].filter(Boolean).join(' ')}
|
||||
<OptionCorrectnessBadges {labels} showCorrect={isCorrect} showIncorrect={isSelected && !isCorrect} />
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user