diff --git a/packages/components/plh/index.ts b/packages/components/plh/index.ts index be5a377341..e5649f9044 100644 --- a/packages/components/plh/index.ts +++ b/packages/components/plh/index.ts @@ -1,6 +1,7 @@ import { PlhParentPointCounterComponent } from "./parent-point-counter/parent-point-counter.component"; import { PlhParentPointBoxComponent } from "./parent-point-box/parent-point-box.component"; import { PlhModuleListItemComponent } from "./plh-kids-kw/components/module-list-item/module-list-item.component"; +import { PlhActivityCheckInComponent } from "./plh-kids-kw/components/activity-check-in/activity-check-in.component"; import { PlhCompletionModalComponent } from "./plh-kids-kw/components/completion-modal/completion-modal.component"; import { PlhModuleDetailsHeaderComponent } from "./plh-kids-kw/components/module-details-header/module-details-header.component"; import { PlhBottomNavigationBarComponent } from "./plh-kids-kw/components/bottom-navigation-bar/bottom-navigation-bar.component"; @@ -9,6 +10,7 @@ export { PlhParentPointCounterComponent, PlhParentPointBoxComponent, PlhModuleListItemComponent, + PlhActivityCheckInComponent, PlhCompletionModalComponent, PlhModuleDetailsHeaderComponent, PlhBottomNavigationBarComponent, @@ -18,6 +20,7 @@ export const PLH_COMPONENTS = [ PlhParentPointCounterComponent, PlhParentPointBoxComponent, PlhModuleListItemComponent, + PlhActivityCheckInComponent, PlhCompletionModalComponent, PlhModuleDetailsHeaderComponent, PlhBottomNavigationBarComponent, @@ -28,6 +31,7 @@ export const PLH_COMPONENT_MAPPING = { parent_point_box: PlhParentPointBoxComponent, plh_module_details_header: PlhModuleDetailsHeaderComponent, plh_module_list_item: PlhModuleListItemComponent, + plh_activity_check_in: PlhActivityCheckInComponent, plh_completion_modal: PlhCompletionModalComponent, plh_bottom_nav: PlhBottomNavigationBarComponent, }; diff --git a/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.html b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.html new file mode 100644 index 0000000000..f402b2c93f --- /dev/null +++ b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.html @@ -0,0 +1,52 @@ +@if (locked()) { +
+ @if (params.countdownStartDate) { +
+
+
+
+
{{ countdownText() }}
+
+ } + +
+ +
+
+
+

{{ params.title }}

+
+
+ +
+
+
+} @else { +
+
+
+ +
+
+
+

{{ params.title }}

+
+
+ +
+
+
+
+} diff --git a/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.scss b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.scss new file mode 100644 index 0000000000..613bd87fb5 --- /dev/null +++ b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.scss @@ -0,0 +1,85 @@ +.activity-container { + max-width: 168px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .progress-tracker { + width: 100%; + padding: 0 12px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + .progress-bar-container { + width: 40%; + background-color: var(--ion-color-gray-200); + height: 14px; + border-radius: 50px; + overflow: hidden; + .progress-bar { + border-radius: 50px; + height: 100%; + background-color: #e0b160; + transition: + width 0.5s ease, + background-color 0.5s ease; + } + } + .countdown-text { + margin-left: 8px; + color: var(--ion-color-gray-600); + font-weight: var(--font-weight-medium); + } + } + .image { + height: 150px; + img { + padding: 4px; + height: 100%; + width: 100%; + object-fit: contain; + } + } + .details { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: fit-content; + margin: 0 auto; + .title h4 { + text-align: center; + color: var(--ion-color-gray-600); + font-weight: var(--font-weight-bold); + } + .icon { + display: flex; + align-items: center; + margin: 0 4px; + img { + width: 28px; + } + } + } +} +.activity-container[data-language-direction~="rtl"] { + .progress-tracker { + .progress-bar-container { + margin-right: 0; + margin-left: 6px; + } + } + .image { + -webkit-transform: scaleX(-1); + transform: scaleX(-1); + } + .details { + .icon { + img { + -webkit-transform: scaleX(-1); + transform: scaleX(-1); + } + } + } +} diff --git a/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.spec.ts b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.spec.ts new file mode 100644 index 0000000000..36f89a12df --- /dev/null +++ b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { IonicModule } from "@ionic/angular"; + +import { PlhActivityCheckInComponent } from "./activity-check-in.component"; + +describe("ActivityCheckInComponent", () => { + let component: PlhActivityCheckInComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [PlhActivityCheckInComponent], + imports: [IonicModule.forRoot()], + }).compileComponents(); + + fixture = TestBed.createComponent(PlhActivityCheckInComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.ts b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.ts new file mode 100644 index 0000000000..42bdc7f55b --- /dev/null +++ b/packages/components/plh/plh-kids-kw/components/activity-check-in/activity-check-in.component.ts @@ -0,0 +1,124 @@ +import { Component, computed, OnInit, signal } from "@angular/core"; +import { addDays, differenceInCalendarDays, isValid, parseISO, startOfDay } from "date-fns"; +import { TemplateBaseComponent } from "src/app/shared/components/template/components/base"; +import { TemplateTranslateService } from "src/app/shared/components/template/services/template-translate.service"; +import { + getNumberParamFromTemplateRow, + getParamFromTemplateRow, + getStringParamFromTemplateRow, +} from "src/app/shared/utils"; + +interface IActivityCheckInParams { + /* TEMPLATE PARAMETER: "title". The title attached at the bottom of the component */ + title?: string; + /* TEMPLATE PARAMETER: "locked_icon_asset". The icon that shows when the activity is locked */ + lockedIconAsset?: string; + /* TEMPLATE PARAMETER: "locked_image_asset". The illustration that shows when the activity is locked */ + lockedImageAsset?: string; + /* TEMPLATE PARAMETER: "unlocked_icon_asset". The icon that shows when the activity is unlocked */ + unlockedIconAsset?: string; + /* TEMPLATE PARAMETER: "unlocked_image_asset". The illustration that shows when the activity is locked */ + unlockedImageAsset?: string; + /* TEMPLATE PARAMETER: "countdown_start_date". The date when the countdown is initially started */ + countdownStartDate?: Date; + /* TEMPLATE PARAMETER: "countdown_days". The number of days after the start date that the activity is unlocked */ + countDownDays?: number; + /* TEMPLATE PARAMETER: "countdown_text_list". A list of text strings relating to the number of days left to countdown, in order from fewest to most */ + countdownTextList?: string[]; +} + +@Component({ + selector: "plh-activity-check-in", + templateUrl: "./activity-check-in.component.html", + styleUrls: ["./activity-check-in.component.scss"], +}) +export class PlhActivityCheckInComponent extends TemplateBaseComponent implements OnInit { + params: Partial = {}; + + daysUntilUnlock = signal(Infinity); + locked = computed(() => { + // If no start date is provided, activity should be locked + if (!this.params.countdownStartDate) return true; + return this.daysUntilUnlock() && this.daysUntilUnlock() > 0; + }); + countdownText = computed(() => { + const targetIndex = Math.min( + this.daysUntilUnlock() - 1, + this.params.countdownTextList.length - 1 + ); + return this.params.countdownTextList[targetIndex]; + }); + progressPercentage = computed(() => { + if (this.daysUntilUnlock() === Infinity) return 0; + return ( + ((this.params.countDownDays - (this.daysUntilUnlock() || 0)) / this.params.countDownDays) * + 100 + ); + }); + + constructor(public templateTranslateService: TemplateTranslateService) { + super(); + } + + ngOnInit() { + this.getParams(); + this.calculateDaysUntilUnlock(); + } + + private getParams() { + this.params.title = getStringParamFromTemplateRow(this._row, "title", null); + this.params.lockedIconAsset = getStringParamFromTemplateRow( + this._row, + "locked_icon_asset", + null + ); + this.params.lockedImageAsset = getStringParamFromTemplateRow( + this._row, + "locked_image_asset", + null + ); + this.params.unlockedIconAsset = getStringParamFromTemplateRow( + this._row, + "unlocked_icon_asset", + null + ); + this.params.unlockedImageAsset = getStringParamFromTemplateRow( + this._row, + "unlocked_image_asset", + null + ); + this.params.countDownDays = getNumberParamFromTemplateRow(this._row, "countdown_days", 6); + const countdownStartDate = getParamFromTemplateRow(this._row, "countdown_start_date", null); + let parsedDate = null; + if (countdownStartDate) { + const attemptDate = new Date(countdownStartDate); + if (isValid(attemptDate)) { + parsedDate = attemptDate; + } else { + const attemptISO = parseISO(countdownStartDate); + if (isValid(attemptISO)) { + parsedDate = attemptISO; + } + } + } + this.params.countdownStartDate = parsedDate; + + let countdownTextList = getParamFromTemplateRow(this._row, "countdown_text_list", []); + if (typeof countdownTextList === "string") { + countdownTextList = countdownTextList.split(",").map((text) => text.trim()); + } + this.params.countdownTextList = countdownTextList; + } + + private calculateDaysUntilUnlock() { + if (this.params.countDownDays === 0) this.daysUntilUnlock.set(0); + if (this.params.countdownStartDate && this.params.countDownDays) { + const unlockDate = addDays(this.params.countdownStartDate, this.params.countDownDays); + const daysRemaining = differenceInCalendarDays( + startOfDay(unlockDate), + startOfDay(new Date()) + ); + this.daysUntilUnlock.set(Math.max(daysRemaining, 0)); + } + } +} diff --git a/src/app/shared/components/template/components/number-selector/number-selector.component.ts b/src/app/shared/components/template/components/number-selector/number-selector.component.ts index 73c875c2cc..cf15d0af9c 100644 --- a/src/app/shared/components/template/components/number-selector/number-selector.component.ts +++ b/src/app/shared/components/template/components/number-selector/number-selector.component.ts @@ -56,7 +56,7 @@ export class TmplNumberComponent this.displayValue = this.min_value; } // assign any previously saved value - if (this._row.value) { + if (this._row.value || this._row.value === 0) { this.displayValue = this._row.value; } } diff --git a/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.scss b/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.scss index a51f32e80e..0dd8f284c5 100644 --- a/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.scss +++ b/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.scss @@ -53,7 +53,7 @@ $progress-colour: var(--task-progress-bar-color, var(--ion-color-primary)); .task-progress-wheel { .progress-container { position: relative; - width: 150px; + max-width: 150px; height: 150px; margin-bottom: 48px; }