{"version":3,"file":"app-1e35a367.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/./src/resources/elements/announcement-filter/announcement-filter.html","webpack://lernplattform-admin-frontend/./src/resources/elements/announcement-info-card/announcement-info-card.html","webpack://lernplattform-admin-frontend/./src/resources/elements/api-date-filter-input/api-date-filter-input.html","webpack://lernplattform-admin-frontend/./src/resources/elements/announcement-filter/announcement-filter.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/announcement-info-card/announcement-info-card.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/api-date-filter-input/api-date-filter-input.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/api-date-filter-input/index.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"w-100\\\"> <div if.to-view=\\\"!tenantId\\\" class=\\\"row mb-3\\\"> <div class=\\\"col-12\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" t=\\\"globals.tenants\\\"></label> <text-tags-widget disabled.to-view=\\\"disabled\\\" table-columns.to-view=\\\"tenantTableColumns\\\" table-data.to-view=\\\"foundTenants\\\" fixed-height.to-view=\\\"true\\\" tags.to-view=\\\"selectedTenantTags\\\" tags-edited.trigger=\\\"handleTenantEntryRemoved($event)\\\" entry-selected.trigger=\\\"handleTenantEntrySelected($event)\\\" all-selected.trigger=\\\"handleAllTenantEntriesSelected()\\\" can-select-all.to-view=\\\"true\\\" search-string-changed.trigger=\\\"handleTenantSearchStringChanged($event)\\\"></text-tags-widget> <small class=\\\"form-text text-muted\\\" t=\\\"pages.announcements.audienceTenantsInfo\\\"></small> </div> </div> </div> <div class=\\\"row\\\"> <div class=\\\"col-12\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" t=\\\"globals.courses\\\"></label> <text-tags-widget disabled.to-view=\\\"disabled\\\" table-columns.to-view=\\\"courseTableColumns\\\" table-data.to-view=\\\"foundCourses\\\" fixed-height.to-view=\\\"true\\\" tags.to-view=\\\"selectedCoursesTags\\\" tags-edited.trigger=\\\"handleCourseEntryRemoved($event)\\\" entry-selected.trigger=\\\"handleCourseEntrySelected($event)\\\" search-string-changed.trigger=\\\"handleCourseSearchStringChanged($event)\\\"></text-tags-widget> <small class=\\\"form-text text-muted\\\" t=\\\"pages.announcements.audienceCoursesInfo\\\"></small> </div> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"announcement-info-card\\\"> <div class=\\\"announcement-info-card__card\\\"> <label if.to-view=\\\"selectMode\\\" class=\\\"announcement-info-card__select custom-control custom-checkbox\\\"> <input type=\\\"checkbox\\\" class=\\\"custom-control-input announcement-info-card__select-checkbox\\\" checked.to-view=\\\"selected\\\"> <span class=\\\"custom-control-label\\\"></span> </label> <div class=\\\"announcement-info-card__wrapper ${ selected ? 'announcement-info-card__wrapper--ghost' : ''}\\\"> <x-icon class=\\\"announcement-info-card__header-image ${ archived ? 'announcement-info-card__header-image--archived' : '' }\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\" icon-name.to-view=\\\"`Announcements/ga-image`\\\"> </x-icon> <div class=\\\"announcement-info-card__title\\\">${title}</div> <div class=\\\"announcement-info-card__button-wrapper\\\"> <p class=\\\"announcement-info-card__button ${ selectMode ? 'announcement-info-card__button--bg' : '' }\\\" t=\\\"${ archived ? 'globals.show' : 'globals.edit'}\\\" click.delegate=\\\"startEditing()\\\"></p> </div> </div> </div> <div class=\\\"announcement-info-card__info-text\\\" t.to-view=\\\"textTk\\\" t-params.to-view=\\\"textTkParam\\\"></div> <div if.to-view=\\\"!archived\\\" class=\\\"announcement-info-card__info-text\\\" t=\\\"pages.announcements.audienceReachedInfo\\\" t-params.to-view=\\\"{ read: readCount , total: audienceCount }\\\"></div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"api-date-filter-input api-date-filter-input--theme-${theme}\\\"> <div class=\\\"form-group\\\"> <label if.to-view=\\\"showLabel\\\" class=\\\"form-label\\\" t.to-view=\\\"labelTk\\\"></label> <div class=\\\"input-group api-date-filter-input__filter-group\\\"> <input class=\\\"form-control api-date-filter-input__filter-text\\\" type=\\\"text\\\" t=\\\"[placeholder]${placeholderTk || 'globals.filter'};[aria-label]globals.filterBy\\\" ref=\\\"flatPickerElement\\\" disabled.to-view=\\\"readonly\\\" data-input> <span if.to-view=\\\"internalStartDate || internalEndDate\\\" class=\\\"input-group-append\\\"> <button class=\\\"btn btn-secondary m-0 p-0 api-date-filter-input__filter-button\\\" click.delegate=\\\"clearPicker()\\\" readonly.to-view=\\\"readonly\\\" type=\\\"button\\\"> <safe-icon class=\\\"api-date-filter-input__clear\\\" icon-name=\\\"System/Close\\\"></safe-icon> </button> </span> </div> </div> </template> \";\n// Exports\nexport default code;","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { ITenantInfoDto } from 'common/dto/tenant/management/tenant.management.interface';\nimport { ArrayUtils } from 'common/utils/ArrayUtils';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { EntrySelectedEvent, ITextTagsWidgetTableColumn, SearchStringChangedEvent, TagsEditedEvent } from '../../elements/text-tags-widget/text-tags-widget';\n\n@autoinject()\nexport class AnnouncementFilter {\n  @bindable() public tenantId: number | null = null;\n  @bindable() public tenantIds: number[] = [];\n  @bindable() public courseIds: number[] = [];\n  @bindable() public disabled = false;\n\n  private foundTenants: ITenantInfoDto[] = [];\n  private foundCourses: ICourseEditResponseDto[] = [];\n\n  private availableTenants: ITenantInfoDto[] = [];\n  private availableCourses: ICourseEditResponseDto[] = [];\n\n  private selectedTenants: ITenantInfoDto[] = [];\n  private selectedCourses: ICourseEditResponseDto[] = [];\n  private tenantTableColumns: ITextTagsWidgetTableColumn<ITenantInfoDto>[] = [\n    {\n      transformer: e => e.name\n    },\n  ];\n  private courseTableColumns: ITextTagsWidgetTableColumn<ICourseEditResponseDto>[] = [\n    {\n      transformer: e => e.title\n    },\n    {\n      transformer: e => e.language\n    },\n  ];\n\n  constructor(\n    private tenantApi: TenantApiService,\n    private courseManagementApiService: CourseManagementApiService\n  ) { }\n\n  protected attached(): void {\n    this.fetchData();\n  }\n\n  private async fetchData(): Promise<void> {\n    this.updateAvailableCourses();\n    this.updateAvailableTenants();\n  }\n\n  private courseIdsChanged(): void {\n    if(!this.courseIds.length) return;\n    this.updateAvailableCourses();\n  }\n\n  private tenantIdsChanged(): void {\n    if(!this.tenantIds.length) return;\n    this.updateAvailableTenants();\n  }\n\n  private async updateAvailableTenants(): Promise<void> {\n    const filter: IFilterDto = {\n      limit: 500,\n      order: [{\n        field: 'name',\n        dir: ColumnSortDirection.ASC,\n      }],\n      filter: [{\n        field: 'enabled',\n        type: FilterOperationType.Eq,\n        value: true\n      }]\n    };\n    this.availableTenants = this.tenantId ? [] : (await this.tenantApi.getTenants(filter)).rows;\n    this.updateTenantData();\n  }\n\n  private async updateAvailableCourses(): Promise<void> {\n    this.availableCourses = this.tenantId\n      ? await this.tenantApi.getAvailableRegularCourses()\n      : await this.courseManagementApiService.getAvailableRegularCourses();\n    this.updateCourseData();\n  }\n\n  private updateTenantData(searchString?: string): void {\n    this.selectedTenants = this.availableTenants.filter(tenant => this.tenantIds.includes(tenant.id));\n    const selectedTenantIds = this.selectedTenants.map(tenant => tenant.id);\n    this.foundTenants = this.availableTenants.filter(tenant =>\n      !selectedTenantIds.includes(tenant.id)\n      && (searchString != null ? tenant.name.toLocaleLowerCase().includes(searchString.toLocaleLowerCase()) : true)\n    );\n    this.foundTenants = ArrayUtils.immutableSort(this.foundTenants, (a, b) => a.name.localeCompare(b.name));\n  }\n\n  private updateCourseData(searchString?: string): void {\n    this.selectedCourses = this.availableCourses.filter(course => this.courseIds.includes(course.id));\n    const selectedCourseIds = this.selectedCourses.map(course => course.id);\n    this.foundCourses = this.availableCourses.filter(course =>\n      !selectedCourseIds.includes(course.id)\n      && (searchString != null ? course.title.toLocaleLowerCase().includes(searchString.toLocaleLowerCase()) : true)\n    );\n    this.foundCourses = ArrayUtils.immutableSort(this.foundCourses, (a, b) => a.title.localeCompare(b.title));\n  }\n\n  @computedFrom('selectedTenants.length')\n  private get selectedTenantTags(): string[] {\n    return this.selectedTenants.map(tenant => tenant.name);\n  }\n\n  @computedFrom('selectedCourses.length')\n  private get selectedCoursesTags(): string[] {\n    return this.selectedCourses.map(course => course.title);\n  }\n\n  private async handleTenantSearchStringChanged(event: SearchStringChangedEvent): Promise<void> {\n    this.updateTenantData(event.detail.searchString.trim());\n  }\n\n  private async handleCourseSearchStringChanged(event: SearchStringChangedEvent): Promise<void> {\n    this.updateCourseData(event.detail.searchString.trim());\n  }\n\n  private handleTenantEntrySelected(event: EntrySelectedEvent<ITenantInfoDto>): void {\n    const selected = event.detail.entry;\n    if(!this.selectedTenants.find(tenant => tenant.id === selected.id)) {\n      this.selectedTenants.push(selected);\n      this.tenantIds = this.selectedTenants.map(tenant => tenant.id);\n      this.updateTenantData();\n    }\n  }\n\n  private handleAllTenantEntriesSelected(): void {\n    this.selectedTenants = this.availableTenants;\n    this.tenantIds = this.selectedTenants.map(tenant => tenant.id);\n    this.updateTenantData();\n  }\n\n  private handleCourseEntrySelected(event: EntrySelectedEvent<ICourseEditResponseDto>): void {\n    const selected = event.detail.entry;\n    if(!this.selectedCourses.find(course => course.id === selected.id)) {\n      this.selectedCourses.push(selected);\n      this.courseIds = this.selectedCourses.map(tenant => tenant.id);\n      this.updateCourseData();\n    }\n  }\n\n  private handleTenantEntryRemoved(event: TagsEditedEvent): void {\n    this.selectedTenants = this.selectedTenants.filter(tenant => event.detail.tags.includes(tenant.name));\n    this.tenantIds = this.selectedTenants.map(tenant => tenant.id);\n    this.updateTenantData();\n  }\n\n  private handleCourseEntryRemoved(event: TagsEditedEvent): void {\n    this.selectedCourses = this.selectedCourses.filter(course => event.detail.tags.includes(course.title));\n    this.courseIds = this.selectedCourses.map(tenant => tenant.id);\n    this.updateTenantData();\n  }\n}\n","import { AnnouncementApiService } from 'services/api/AnnouncementApiService';\nimport { autoinject, bindable } from 'aurelia-framework';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport Moment from 'moment';\nimport { LogUtils } from 'common/utils/LogUtils';\n\n@autoinject()\nexport class AnnouncementInfoCard {\n  @bindable() public title = '';\n  @bindable() public id: number | null = null;\n  @bindable() public readCount = 0;\n  @bindable() public selected = false;\n  @bindable() public selectMode = false;\n  @bindable() public visibleFrom = '';\n  @bindable() public visibleTo = '';\n  @bindable() public archived = false;\n\n  private textTk = '';\n  private textTkParam = { date: '' };\n  private audienceCount = 0;\n\n  constructor(\n    private element: Element,\n    private announcementApiService: AnnouncementApiService,\n  ) {}\n\n  private attached(): void {\n    this.prepareVisibilityTimeMessage();\n  }\n\n  private async idChanged(): Promise<void> {\n    if(this.id == null) return;\n    try {\n      this.audienceCount = await this.announcementApiService.getAnnouncementAudienceCount(this.id);\n    } catch(e) {\n      LogUtils.exception(e);\n      throw e;\n    }\n  }\n\n  private prepareVisibilityTimeMessage(): void {\n    if(this.visibleFrom === '' || this.visibleTo === '') return;\n    if(Moment(this.visibleFrom).isAfter(Date.now())) {\n      this.textTk = 'pages.announcements.visibilityFromDate';\n      this.textTkParam = { date: Moment(this.visibleFrom).format('DD.MM.YYYY') };\n    } else if(Moment(this.visibleTo).isAfter(Date.now())) {\n      this.textTk = 'pages.announcements.visibilityToDate';\n      this.textTkParam = { date: Moment(this.visibleTo).format('DD.MM.YYYY') };\n    } else {\n      this.textTk = 'pages.announcements.visibilityExpiredSince';\n      this.textTkParam = { date: Moment(this.visibleTo).format('DD.MM.YYYY') };\n    }\n  }\n\n  private startEditing(): void {\n    DomUtils.fireEvent(this.element, { name: 'edit-me', bubbles: false });\n  }\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { FilterChangedEvent, FilterInputTheme } from 'common/dto/filter/filter.interface';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport flatpickr from 'flatpickr';\nimport l10n from 'flatpickr/dist/l10n';\nimport { Instance } from 'flatpickr/dist/types/instance';\nimport { key as LocaleKey } from 'flatpickr/dist/types/locale';\nimport { DateService } from '../../../services/DateService';\n\n/**\n * @event {FilterChangedEvent} filter-changed\n */\n@autoinject()\nexport class ApiDateFilterInput {\n  @bindable() public startDate: Date | null = null;\n  @bindable() public endDate: Date | null = null;\n  @bindable() public labelTk: string | null = null;\n  @bindable() public placeholderTk: string | null = null;\n  @bindable() public setValue: string | null = null;\n  @bindable() public theme: FilterInputTheme = FilterInputTheme.Default;\n  @bindable() public initialValue: string | null = null;\n  @bindable() public readonly: boolean = false;\n\n  protected flatPickerElement!: HTMLElement;\n  protected flatPickerInstance?: Instance;\n\n  private ready = false;\n  private internalStartDate: Date | null = null;\n  private internalEndDate: Date | null = null;\n  private element: HTMLElement;\n\n  private currentFilter: string = '|';\n  // date in 10 years as placeholder for second date when only one date is selected\n  private readonly MAX_DATE = new Date(new Date().setFullYear(new Date().getFullYear() + 10));\n\n  constructor(\n    element: Element,\n    private i18n: I18N,\n    private dateService: DateService,\n  ) {\n    this.element = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    const locale = this.i18n.getLocale();\n    this.internalStartDate = this.startDate;\n    this.internalEndDate = this.endDate;\n    if(this.initialValue) {\n      this.setDatesFromStringValue(this.initialValue);\n    }\n\n    // Timeout is needed, as otherwise the bindables might not be ready yet\n    setTimeout(() => {\n      if(this.flatPickerElement == null) return;\n      this.flatPickerInstance = flatpickr(this.flatPickerElement, {\n        mode: 'range',\n        locale: l10n[locale as LocaleKey],\n        allowInput: true,\n        disableMobile: true,\n        dateFormat: 'd.m.Y',\n        onReady: (__, _, instance) => {\n          if(this.setValue) this.setDatesFromStringValue(this.setValue);\n          instance.input.value = this.getInputString(this.internalStartDate, this.internalEndDate);\n          this.ready = true;\n          this.updateTimeRangeFilter();\n        },\n        onOpen: () => {\n          this.clearPicker();\n        },\n        onChange: selectedDates => {\n          [this.internalStartDate, this.internalEndDate] = selectedDates;\n          this.updateTimeRangeFilter();\n        },\n        onClose: (selectedDates, _, instance) => {\n          if(selectedDates.length === 1) {\n            instance.setDate([selectedDates[0], this.MAX_DATE], true);\n          }\n        },\n      });\n    });\n  }\n\n  protected detached(): void {\n    this.flatPickerInstance?.destroy();\n  }\n\n  @computedFrom('theme')\n  private get showLabel(): boolean {\n    return this.theme === FilterInputTheme.Default;\n  }\n\n  private setValueChanged(): void {\n    if(this.setValue) {\n      this.setDatesFromStringValue(this.setValue);\n    } else {\n      this.internalStartDate = this.startDate;\n      this.internalEndDate = this.endDate;\n    }\n\n    this.updateTimeRangeFilter();\n  }\n\n  private updateTimeRangeFilter(): void {\n    if(this.flatPickerInstance) {\n      this.flatPickerInstance.input.value = this.getInputString(this.internalStartDate, this.internalEndDate);\n    }\n\n    this.emitChangeEvent();\n  }\n\n  private emitChangeEvent(): void {\n    if(!this.ready) return;\n\n    const filter = `${this.internalStartDate?.toISOString() || ''}|${this.internalEndDate?.toISOString() || ''}`;\n    if(filter !== this.currentFilter && !this.dateService.isSameDay(this.internalEndDate, this.MAX_DATE)) {\n      DomUtils.fireEvent<FilterChangedEvent['detail']>(this.element, {\n        bubbles: true,\n        detail: { filter },\n        name: 'filter-changed',\n      });\n      this.currentFilter = filter;\n    }\n  }\n\n  private getInputString(startDate: Date | null, endDate: Date | null): string {\n    let inputString = '';\n\n    if(startDate && (!endDate || this.dateService.isSameDay(endDate, this.MAX_DATE))) {\n      inputString = `${this.i18n.tr('elements.datePicker.since')}  ${this.dateService.formatDate(startDate)}`;\n    } else if(startDate && endDate) {\n      inputString = `${this.dateService.formatDate(startDate)} ${this.i18n.tr('elements.datePicker.to')} ${this.dateService.formatDate(endDate)}`;\n    }\n\n    return inputString;\n  }\n\n  private clearPicker(): void {\n    this.flatPickerInstance?.clear(true, true);\n    this.updateTimeRangeFilter();\n  }\n\n  private setDatesFromStringValue(value: string | null): void {\n    const values = (value ?? '').split('|').map(v => v ? new Date(v) : null);\n    [this.internalStartDate, this.internalEndDate] = values;\n  }\n}\n","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/api-date-filter-input/api-date-filter-input'));\n}"],"names":[],"sourceRoot":""}