diff --git a/libs/oms/data-access/src/lib/models/return-process-question-key.ts b/libs/oms/data-access/src/lib/models/return-process-question-key.ts index c95cdb3e7..1110c0d16 100644 --- a/libs/oms/data-access/src/lib/models/return-process-question-key.ts +++ b/libs/oms/data-access/src/lib/models/return-process-question-key.ts @@ -6,6 +6,7 @@ export const ReturnProcessQuestionKey = { DisplayCondition: 'display_condition', DevicePower: 'device_power', PackageComplete: 'package_complete', + PackageIncomplete: 'package_incomplete', CaseCondition: 'case_condition', UsbPort: 'usb_port', } as const; diff --git a/libs/oms/data-access/src/lib/models/return-process-question-type.ts b/libs/oms/data-access/src/lib/models/return-process-question-type.ts index c740cd90e..5342cc971 100644 --- a/libs/oms/data-access/src/lib/models/return-process-question-type.ts +++ b/libs/oms/data-access/src/lib/models/return-process-question-type.ts @@ -1,6 +1,7 @@ export const ReturnProcessQuestionType = { Select: 'select', Product: 'product', + Checklist: 'checklist', } as const; export type ReturnProcessQuestionType = diff --git a/libs/oms/data-access/src/lib/models/return-process-question.ts b/libs/oms/data-access/src/lib/models/return-process-question.ts index 6ce49169e..b4603c27f 100644 --- a/libs/oms/data-access/src/lib/models/return-process-question.ts +++ b/libs/oms/data-access/src/lib/models/return-process-question.ts @@ -7,20 +7,44 @@ export interface ReturnProcessQuestionBase { type: ReturnProcessQuestionType; } +export interface ReturnProcessQuestionOptions { + label: string; + value: string; +} + +export interface ReturnProcessChecklistQuestion + extends ReturnProcessQuestionBase { + type: typeof ReturnProcessQuestionType.Checklist; + options: ReturnProcessChecklistQuestionOptions[]; + other?: { + label: string; + value?: string; + }; + nextQuestion?: ReturnProcessQuestionKey; +} + +export type ReturnProcessChecklistQuestionOptions = + ReturnProcessQuestionOptions; + export interface ReturnProcessSelectQuestion extends ReturnProcessQuestionBase { type: typeof ReturnProcessQuestionType.Select; options: ReturnProcessQuestionSelectOptions[]; } -export interface ReturnProcessQuestionSelectOptions { +export interface ReturnProcessQuestionSelectOptions + extends ReturnProcessQuestionOptions { label: string; value: string; nextQuestion?: ReturnProcessQuestionKey; } -export interface ReturnProcessProductQuestion extends ReturnProcessQuestionBase { +export interface ReturnProcessProductQuestion + extends ReturnProcessQuestionBase { type: typeof ReturnProcessQuestionType.Product; nextQuestion?: ReturnProcessQuestionKey; } -export type ReturnProcessQuestion = ReturnProcessSelectQuestion | ReturnProcessProductQuestion; +export type ReturnProcessQuestion = + | ReturnProcessSelectQuestion + | ReturnProcessProductQuestion + | ReturnProcessChecklistQuestion; diff --git a/libs/oms/data-access/src/lib/questions/constants.ts b/libs/oms/data-access/src/lib/questions/constants.ts index a95565290..226efd640 100644 --- a/libs/oms/data-access/src/lib/questions/constants.ts +++ b/libs/oms/data-access/src/lib/questions/constants.ts @@ -33,3 +33,12 @@ export const enum ItemDefectiveValue { Yes = 'yes', No = 'no', } + +export const enum PackageIncompleteValue { + // Karton/Umverpackung + Ovp = 'ovp', + // Ladekabel + CharchingCable = 'charching_cable', + // Quickstart Guide + QuickstartGuide = 'quickstart_guide', +} diff --git a/libs/oms/data-access/src/lib/questions/tolino.spec.ts b/libs/oms/data-access/src/lib/questions/tolino.spec.ts index 3cdd638b0..075f1a22f 100644 --- a/libs/oms/data-access/src/lib/questions/tolino.spec.ts +++ b/libs/oms/data-access/src/lib/questions/tolino.spec.ts @@ -12,39 +12,6 @@ describe('Tolino Return Process', () => { const result = validateTolinoQuestions(answers); expect(result).toEqual({ state: EligibleForReturnState.Eligible }); }); - - it('should return Eligible when damaged and has significant defects', () => { - const answers = { - [ReturnProcessQuestionKey.ItemCondition]: 'damaged', - [ReturnProcessQuestionKey.DevicePower]: 'yes', - }; - const result = validateTolinoQuestions(answers); - expect(result).toEqual({ state: EligibleForReturnState.Eligible }); - }); - - it('should return NotEligible when damaged but no significant defects', () => { - const answers = { - [ReturnProcessQuestionKey.ItemCondition]: 'damaged', - [ReturnProcessQuestionKey.DevicePower]: 'no', - [ReturnProcessQuestionKey.PackageComplete]: 'no', - [ReturnProcessQuestionKey.CaseCondition]: 'no', - [ReturnProcessQuestionKey.UsbPort]: 'no', - }; - const result = validateTolinoQuestions(answers); - expect(result).toEqual({ - state: EligibleForReturnState.NotEligible, - reason: 'No significant defects found', - }); - }); - - it('should return NotEligible for invalid answers', () => { - const answers = {}; - const result = validateTolinoQuestions(answers); - expect(result).toEqual({ - state: EligibleForReturnState.NotEligible, - reason: 'Invalid answers', - }); - }); }); describe('Registry mappings', () => { diff --git a/libs/oms/data-access/src/lib/questions/tolino.ts b/libs/oms/data-access/src/lib/questions/tolino.ts index 036de7982..7175acab8 100644 --- a/libs/oms/data-access/src/lib/questions/tolino.ts +++ b/libs/oms/data-access/src/lib/questions/tolino.ts @@ -9,6 +9,7 @@ import { import { ItemConditionValue, ItemDefectiveValue, + PackageIncompleteValue, ReturnReasonValue, } from './constants'; @@ -60,7 +61,7 @@ export const tolinoQuestions: ReturnProcessQuestion[] = [ { label: 'Ja', value: ItemDefectiveValue.No, - nextQuestion: ReturnProcessQuestionKey.ReturnReason, + nextQuestion: ReturnProcessQuestionKey.PackageIncomplete, }, { label: 'Nein', @@ -69,6 +70,29 @@ export const tolinoQuestions: ReturnProcessQuestion[] = [ }, ], }, + { + key: ReturnProcessQuestionKey.PackageIncomplete, + description: 'Was fehlt?', + type: ReturnProcessQuestionType.Checklist, + options: [ + { + label: 'Karton/Umverpackung', + value: PackageIncompleteValue.Ovp, + }, + { + label: 'Ladekabel', + value: PackageIncompleteValue.CharchingCable, + }, + { + label: 'Quickstart Guide', + value: PackageIncompleteValue.QuickstartGuide, + }, + ], + other: { + label: 'Sonstiges', + }, + nextQuestion: ReturnProcessQuestionKey.CaseCondition, + }, { key: ReturnProcessQuestionKey.CaseCondition, description: 'Hat das Gehäuse Mängel oder ist zerkratzt?', @@ -137,45 +161,11 @@ export const tolinoQuestions: ReturnProcessQuestion[] = [ /** * Validates the answers for the Tolino return process and determines return eligibility. - * - * The validation logic follows these rules: - * 1. If item is in original packaging, it's always eligible for return - * 2. If item is damaged, it requires at least one defect to be eligible - * 3. If no valid condition is provided, the item is not eligible - * * @param {ReturnProcessAnswers} answers - A record of answers keyed by question keys * @returns {EligibleForReturn} Object containing eligibility state and reason if applicable */ export function validateTolinoQuestions( answers: ReturnProcessAnswers, ): EligibleForReturn { - if ( - answers[ReturnProcessQuestionKey.ItemCondition] === - ItemConditionValue.OriginalPackaging - ) { - return { state: EligibleForReturnState.Eligible }; - } else if ( - answers[ReturnProcessQuestionKey.ItemCondition] === - ItemConditionValue.Damaged - ) { - if ( - answers[ReturnProcessQuestionKey.DevicePower] === - ItemDefectiveValue.Yes || - answers[ReturnProcessQuestionKey.PackageComplete] === - ItemDefectiveValue.Yes || - answers[ReturnProcessQuestionKey.CaseCondition] === - ItemDefectiveValue.Yes || - answers[ReturnProcessQuestionKey.UsbPort] === ItemDefectiveValue.Yes - ) { - return { state: EligibleForReturnState.Eligible }; - } - return { - state: EligibleForReturnState.NotEligible, - reason: 'No significant defects found', - }; - } - return { - state: EligibleForReturnState.NotEligible, - reason: 'Invalid answers', - }; + return { state: EligibleForReturnState.Eligible }; } diff --git a/libs/oms/data-access/src/lib/return-process.service.ts b/libs/oms/data-access/src/lib/return-process.service.ts index 0b2412e39..0908b5e08 100644 --- a/libs/oms/data-access/src/lib/return-process.service.ts +++ b/libs/oms/data-access/src/lib/return-process.service.ts @@ -8,6 +8,7 @@ import { } from './models'; import { CategoryQuestions, CategoryQuestionValidators } from './questions'; import { KeyValue } from '@angular/common'; +import { ReturnProcessChecklistAnswerSchema } from './schemas'; /** * Service responsible for managing the return process workflow. @@ -57,7 +58,9 @@ export class ReturnProcessService { process.productCategory || process.receiptItem.features?.['category']; if (category) { - return CategoryQuestionValidators[category]; + return CategoryQuestionValidators[ + category as keyof typeof CategoryQuestionValidators + ]; } return undefined; } @@ -102,6 +105,16 @@ export class ReturnProcessService { questionKey = process.answers[question.key] ? question.nextQuestion : undefined; + } else if (question.type === ReturnProcessQuestionType.Checklist) { + const answer = ReturnProcessChecklistAnswerSchema.parse( + process.answers[question.key], + ); + + if (answer.options?.length || answer.other) { + questionKey = question.nextQuestion; + } else { + questionKey = undefined; + } } else { console.error('Unknown question type', question); break; diff --git a/libs/oms/data-access/src/lib/schemas/fetch-return-details.ts b/libs/oms/data-access/src/lib/schemas/fetch-return-details.schema.ts similarity index 100% rename from libs/oms/data-access/src/lib/schemas/fetch-return-details.ts rename to libs/oms/data-access/src/lib/schemas/fetch-return-details.schema.ts diff --git a/libs/oms/data-access/src/lib/schemas/index.ts b/libs/oms/data-access/src/lib/schemas/index.ts index 31b442477..cac1ac094 100644 --- a/libs/oms/data-access/src/lib/schemas/index.ts +++ b/libs/oms/data-access/src/lib/schemas/index.ts @@ -1,2 +1,3 @@ -export * from './fetch-return-details'; +export * from './fetch-return-details.schema'; export * from './query-token.schema'; +export * from './return-process-question-answer.schema'; diff --git a/libs/oms/data-access/src/lib/schemas/return-process-question-answer.schema.ts b/libs/oms/data-access/src/lib/schemas/return-process-question-answer.schema.ts new file mode 100644 index 000000000..c64ef0e33 --- /dev/null +++ b/libs/oms/data-access/src/lib/schemas/return-process-question-answer.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const ReturnProcessChecklistAnswerSchema = z.object({ + options: z.array(z.string()).default([]), + other: z.string().optional(), +}); + +export type ReturnProcessChecklistAnswer = z.infer< + typeof ReturnProcessChecklistAnswerSchema +>; diff --git a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.html b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.html new file mode 100644 index 000000000..d30db92e5 --- /dev/null +++ b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.html @@ -0,0 +1,24 @@ +@let q = question(); +@if (q) { +
+ {{ q.description }} +
+
+ + @for (option of q.options; track option.label) { + + } + +
+ @if (q.other) { +
+ + + + } +} diff --git a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.scss b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.scss new file mode 100644 index 000000000..461bfa7d0 --- /dev/null +++ b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.scss @@ -0,0 +1,3 @@ +:host { + @apply grid grid-cols-2 gap-6; +} diff --git a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.ts b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.ts new file mode 100644 index 000000000..083a0f03b --- /dev/null +++ b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-checklist-question/return-process-checklist-question.component.ts @@ -0,0 +1,88 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, + input, + linkedSignal, + untracked, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { + ReturnProcessChecklistAnswer, + ReturnProcessChecklistAnswerSchema, + ReturnProcessChecklistQuestion, + ReturnProcessStore, +} from '@isa/oms/data-access'; +import { + ChipOptionComponent, + ChipsComponent, + CheckboxComponent, + ChecklistComponent, + CheckboxLabelDirective, + ChecklistValueDirective, + TextareaComponent, +} from '@isa/ui/input-controls'; +import { isEqual } from 'lodash'; +import { z } from 'zod'; + +@Component({ + selector: 'oms-feature-return-process-checklist-question', + templateUrl: './return-process-checklist-question.component.html', + styleUrls: ['./return-process-checklist-question.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ChipsComponent, + ChipOptionComponent, + CheckboxComponent, + ChecklistComponent, + ChecklistValueDirective, + CheckboxLabelDirective, + TextareaComponent, + ], +}) +export class ReturnProcessChecklistQuestionComponent { + #returnProcessStore = inject(ReturnProcessStore); + + processId = input.required(); + + question = input.required(); + + currentAnswer = computed(() => { + const currentAnswersRaw = + this.#returnProcessStore.entityMap()[this.processId()]?.answers[ + this.question().key + ]; + + if (!currentAnswersRaw) { + return ReturnProcessChecklistAnswerSchema.parse({}); + } + + return ReturnProcessChecklistAnswerSchema.parse(currentAnswersRaw); + }); + + options = linkedSignal(() => this.currentAnswer().options, { + equal: isEqual, + }); + + other = linkedSignal(() => this.currentAnswer().other, { equal: isEqual }); + + valueChangesEffect = effect(() => { + const options = this.options(); + const other = this.other(); + untracked(() => { + this.updateAnswer({ options, other }); + }); + }); + + updateAnswer(answer: ReturnProcessChecklistAnswer) { + this.#returnProcessStore.setAnswer( + this.processId(), + this.question().key, + answer, + ); + } +} diff --git a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.html b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.html index dbbcd2f65..81dfd39e3 100644 --- a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.html +++ b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.html @@ -20,18 +20,24 @@ @for (question of questions(); track question.key; let last = $last) {
@switch (question.type) { - @case ('select') { + @case (ReturnProcessQuestionType.Select) { } - @case ('product') { + @case (ReturnProcessQuestionType.Product) { } + @case (ReturnProcessQuestionType.Checklist) { + + } @default { {{ question | json }} } diff --git a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.ts b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.ts index e3ef7cf7b..bce52b5fc 100644 --- a/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.ts +++ b/libs/oms/feature/return-process/src/lib/return-process-questions/return-process-questions.component.ts @@ -9,11 +9,13 @@ import { import { ReturnProcess, ReturnProcessQuestion, + ReturnProcessQuestionType, ReturnProcessService, ReturnProcessStore, } from '@isa/oms/data-access'; import { ReturnProcessSelectQuestionComponent } from './return-process-select-question/return-process-select-question.component'; import { ReturnProcessProductQuestionComponent } from './return-process-product-question/return-process-product-question.component'; +import { ReturnProcessChecklistQuestionComponent } from './return-process-checklist-question/return-process-checklist-question.component'; import { DropdownButtonComponent, DropdownOptionComponent, @@ -28,6 +30,7 @@ import { JsonPipe, ReturnProcessSelectQuestionComponent, ReturnProcessProductQuestionComponent, + ReturnProcessChecklistQuestionComponent, DropdownButtonComponent, DropdownOptionComponent, ], @@ -37,6 +40,8 @@ export class ReturnProcessQuestionsComponent { // Renamed service to match naming conventions #returnProcessService = inject(ReturnProcessService); + ReturnProcessQuestionType = ReturnProcessQuestionType; + returnProcessId = input.required(); returnProcess = computed(() => { diff --git a/libs/ui/input-controls/src/index.ts b/libs/ui/input-controls/src/index.ts index 0c4c28456..40b22105d 100644 --- a/libs/ui/input-controls/src/index.ts +++ b/libs/ui/input-controls/src/index.ts @@ -1,7 +1,11 @@ +export * from './lib/checkbox/checkbox-label.directive'; export * from './lib/checkbox/checkbox.component'; +export * from './lib/checkbox/checklist-value.directive'; +export * from './lib/checkbox/checklist.component'; export * from './lib/core/input-control.directive'; export * from './lib/dropdown/dropdown.component'; export * from './lib/dropdown/dropdown.types'; export * from './lib/text-field/text-field.component'; +export * from './lib/text-field/textarea.component'; export * from './lib/chips/chips.component'; export * from './lib/chips/chip-option.component'; diff --git a/libs/ui/input-controls/src/input-controls.scss b/libs/ui/input-controls/src/input-controls.scss index eed9852c2..10f11077d 100644 --- a/libs/ui/input-controls/src/input-controls.scss +++ b/libs/ui/input-controls/src/input-controls.scss @@ -1,4 +1,6 @@ -@use './lib/checkbox/checkbox'; -@use './lib/chips/chips'; -@use './lib/dropdown/dropdown'; -@use './lib/text-field/text-field.scss'; +@use "./lib/checkbox/checkbox"; +@use "./lib/checkbox/checklist"; +@use "./lib/chips/chips"; +@use "./lib/dropdown/dropdown"; +@use "./lib/text-field/text-field"; +@use "./lib/text-field/textarea"; diff --git a/libs/ui/input-controls/src/lib/checkbox/_checkbox.scss b/libs/ui/input-controls/src/lib/checkbox/_checkbox.scss index ed78d464d..7b898a096 100644 --- a/libs/ui/input-controls/src/lib/checkbox/_checkbox.scss +++ b/libs/ui/input-controls/src/lib/checkbox/_checkbox.scss @@ -1,3 +1,11 @@ +.ui-checkbox-label { + @apply inline-flex items-center gap-4 text-isa-neutral-900 isa-text-body-2-regular; +} + +.ui-checkbox-label:has(:checked) { + @apply isa-text-body-2-bold; +} + .ui-checkbox.ui-checkbox__checkbox { @apply relative inline-flex p-3 items-center justify-center rounded-lg bg-isa-white size-6 border border-solid border-isa-neutral-900; font-size: 1.5rem; diff --git a/libs/ui/input-controls/src/lib/checkbox/_checklist.scss b/libs/ui/input-controls/src/lib/checkbox/_checklist.scss new file mode 100644 index 000000000..3a8940208 --- /dev/null +++ b/libs/ui/input-controls/src/lib/checkbox/_checklist.scss @@ -0,0 +1,3 @@ +.ui-checklist { + @apply inline-grid grid-flow-row gap-6; +} diff --git a/libs/ui/input-controls/src/lib/checkbox/checkbox-label.directive.ts b/libs/ui/input-controls/src/lib/checkbox/checkbox-label.directive.ts new file mode 100644 index 000000000..37cc0e03a --- /dev/null +++ b/libs/ui/input-controls/src/lib/checkbox/checkbox-label.directive.ts @@ -0,0 +1,9 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: 'label[uiCheckboxLabel]', + host: { + class: 'ui-checkbox-label', + }, +}) +export class CheckboxLabelDirective {} diff --git a/libs/ui/input-controls/src/lib/checkbox/checkbox.component.ts b/libs/ui/input-controls/src/lib/checkbox/checkbox.component.ts index e8c03ee79..f5f408ea6 100644 --- a/libs/ui/input-controls/src/lib/checkbox/checkbox.component.ts +++ b/libs/ui/input-controls/src/lib/checkbox/checkbox.component.ts @@ -13,7 +13,8 @@ export const CheckboxAppearance = { Checkbox: 'checkbox', } as const; -export type CheckboxAppearance = (typeof CheckboxAppearance)[keyof typeof CheckboxAppearance]; +export type CheckboxAppearance = + (typeof CheckboxAppearance)[keyof typeof CheckboxAppearance]; @Component({ selector: 'ui-checkbox', diff --git a/libs/ui/input-controls/src/lib/checkbox/checklist-value.directive.ts b/libs/ui/input-controls/src/lib/checkbox/checklist-value.directive.ts new file mode 100644 index 000000000..2425a7dca --- /dev/null +++ b/libs/ui/input-controls/src/lib/checkbox/checklist-value.directive.ts @@ -0,0 +1,21 @@ +import { Directive, input, model } from '@angular/core'; + +@Directive({ + selector: '[uiChecklistValue]', + host: { + 'class': 'ui-checklist-value', + '[checked]': 'checked()', + '(change)': 'onChange($event)', + }, +}) +export class ChecklistValueDirective { + uiChecklistValue = input(null); + + checked = model(false); + + onChange(event: Event) { + if (event.target instanceof HTMLInputElement) { + this.checked.set(event.target.checked); + } + } +} diff --git a/libs/ui/input-controls/src/lib/checkbox/checklist.component.ts b/libs/ui/input-controls/src/lib/checkbox/checklist.component.ts new file mode 100644 index 000000000..c15085072 --- /dev/null +++ b/libs/ui/input-controls/src/lib/checkbox/checklist.component.ts @@ -0,0 +1,90 @@ +import { + ChangeDetectionStrategy, + Component, + contentChildren, + model, + effect, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { CdkObserveContent } from '@angular/cdk/observers'; +import { ChecklistValueDirective } from './checklist-value.directive'; +import { isEqual } from 'lodash'; + +@Component({ + selector: 'ui-checklist', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CdkObserveContent], + host: { + '[class]': '["ui-checklist"]', + }, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: ChecklistComponent, + multi: true, + }, + ], +}) +export class ChecklistComponent implements ControlValueAccessor { + value = model([]); + + onChange?: (value: unknown[]) => void; + onTouched?: () => void; + + valueDirectives = contentChildren(ChecklistValueDirective, { + descendants: true, + }); + + valueDirectivesEffect = effect(() => { + const valueDirectives = this.valueDirectives(); + + const currentValue = this.value() ?? []; + const nextValue = structuredClone(currentValue); + + for (const directive of valueDirectives) { + const value = directive.uiChecklistValue(); + const checked = directive.checked(); + + if (checked) { + if (!nextValue.includes(value)) { + nextValue.push(value); + } + } else { + const index = nextValue.indexOf(value); + if (index !== -1) { + nextValue.splice(index, 1); + } + } + } + + if (isEqual(currentValue, nextValue)) { + return; + } + + this.value.set(nextValue); + this.onChange?.(nextValue); + this.onTouched?.(); + }); + + writeValue(obj: unknown): void { + if (Array.isArray(obj)) { + this.value.set(obj); + for (const directive of this.valueDirectives()) { + const value = directive.uiChecklistValue(); + const checked = obj.includes(value); + directive.checked.set(checked); + } + } else { + this.value.set([]); + } + } + + registerOnChange(fn: (value: unknown[]) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } +} diff --git a/libs/ui/input-controls/src/lib/text-field/_textarea.scss b/libs/ui/input-controls/src/lib/text-field/_textarea.scss new file mode 100644 index 000000000..6beb414b1 --- /dev/null +++ b/libs/ui/input-controls/src/lib/text-field/_textarea.scss @@ -0,0 +1,24 @@ +.ui-textarea { + @apply px-2 py-1; + @apply bg-white text-isa-neutral-900 isa-text-body-2-regular; + @apply inline-flex flex-col gap-1; + @apply border border-solid rounded-lg; + + textarea { + &:focus { + @apply outline-none; + } + + &:placeholder { + @apply text-isa-neutral-400; + } + } +} + +.ui-textarea__empty { + @apply border-isa-neutral-600; +} + +.ui-textarea__not-empty { + @apply border-isa-neutral-900; +} diff --git a/libs/ui/input-controls/src/lib/text-field/textarea.component.html b/libs/ui/input-controls/src/lib/text-field/textarea.component.html new file mode 100644 index 000000000..ccedd768a --- /dev/null +++ b/libs/ui/input-controls/src/lib/text-field/textarea.component.html @@ -0,0 +1,4 @@ +@if (label()) { +
{{ label() }}
+} + diff --git a/libs/ui/input-controls/src/lib/text-field/textarea.component.ts b/libs/ui/input-controls/src/lib/text-field/textarea.component.ts new file mode 100644 index 000000000..d90fcaa05 --- /dev/null +++ b/libs/ui/input-controls/src/lib/text-field/textarea.component.ts @@ -0,0 +1,19 @@ +import { Component, computed, contentChild, input } from '@angular/core'; +import { NgControl } from '@angular/forms'; + +@Component({ + selector: 'ui-textarea', + templateUrl: './textarea.component.html', + host: { '[class]': '["ui-textarea", controlEmptyClass()]' }, +}) +export class TextareaComponent { + label = input(); + + ngControl = contentChild(NgControl); + + controlEmptyClass = computed(() => { + return this.ngControl()?.value + ? 'ui-textarea__not-empty' + : 'ui-textarea__empty'; + }); +}