feat: job portal

fix: disable function scoping lint rule
fix: outdated contributing docs for adding SCThings
This commit is contained in:
theld
2023-10-02 20:34:49 +00:00
committed by Thea Schöbl
parent f3ba8af051
commit 06b8ca109e
42 changed files with 626 additions and 65 deletions

View File

@@ -46,6 +46,7 @@
"unicorn/no-nested-ternary": "off",
"unicorn/better-regex": "off",
"unicorn/no-non-null-assertion": "off",
"unicorn/consistent-function-scoping": ["error", {"checkArrowFunctions": false}],
"jsdoc/no-types": "error",
"jsdoc/require-param": "off",
"jsdoc/require-param-description": "error",

View File

@@ -129,7 +129,9 @@ describe('dashboard', async function () {
fixture: 'search/types/message/single-message.json',
}).as('search');
cy.get('stapps-news-section').contains('ion-item', 'Mehr Nachrichten').click();
cy.get('stapps-news-section')
.contains('ion-item', 'Mehr Nachrichten')
.click({scrollBehavior: false, force: true});
cy.url().should('include', '/news');
});
});

View File

@@ -35,6 +35,7 @@ const config: IconConfig = {
'settings',
'info',
'rate_review',
'work',
],
},
codePoints: {

View File

@@ -47,6 +47,7 @@ import {UtilModule} from './util/util.module';
import {initLogger} from './_helpers/ts-logger';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AboutModule} from './modules/about/about.module';
import {JobModule} from './modules/jobs/jobs.module';
import {FavoritesModule} from './modules/favorites/favorites.module';
import {ProfilePageModule} from './modules/profile/profile.module';
import {FeedbackModule} from './modules/feedback/feedback.module';
@@ -147,6 +148,7 @@ export function createTranslateLoader(http: HttpClient) {
HebisModule,
IonicModule.forRoot(),
IonIconModule,
JobModule,
FavoritesModule,
LibraryModule,
HttpClientModule,

View File

@@ -48,11 +48,12 @@ export class AuthHelperService {
private browser: SimpleBrowser,
private alertController: AlertController,
) {
this.userConfigurationMap = (
this.configProvider.getAnyValue('auth') as {
default: SCAuthorizationProvider;
}
).default.endpoints.mapping;
this.userConfigurationMap =
(
this.configProvider.getAnyValue('auth') as {
default: SCAuthorizationProvider;
}
).default?.endpoints.mapping ?? {};
}
public getAuthMessage(provider: SCAuthorizationProviderType, action: IAuthAction | IPAIAAuthAction) {

View File

@@ -48,4 +48,5 @@
<stapps-news-section></stapps-news-section>
<stapps-mensa-section></stapps-mensa-section>
<stapps-favorites-section></stapps-favorites-section>
<stapps-job-section></stapps-job-section>
</ion-content>

View File

@@ -32,6 +32,8 @@ import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {UtilModule} from '../../util/util.module';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {NewsModule} from '../news/news.module';
import {JobSectionComponent} from './sections/jobs-section/job-section.component';
import {JobModule} from '../jobs/jobs.module';
const catalogRoutes: Routes = [
{
@@ -51,6 +53,7 @@ const catalogRoutes: Routes = [
MensaSectionContentComponent,
FavoritesSectionComponent,
DashboardComponent,
JobSectionComponent,
],
imports: [
IonicModule.forRoot(),
@@ -65,6 +68,7 @@ const catalogRoutes: Routes = [
ThingTranslateModule.forChild(),
UtilModule,
NewsModule,
JobModule,
],
providers: [SettingsProvider, TranslatePipe],
})

View File

@@ -0,0 +1,43 @@
<!--
~ Copyright (C) 2023 StApps
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<stapps-section [title]="'dashboard.jobs.title' | translate">
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/jobs']">
<ion-icon slot="icon-only" name="search" size="24"></ion-icon>
</ion-button>
<simple-swiper *ngIf="jobs | async as jobs; else noItems" @fade>
<stapps-data-list-item
*ngFor="let item of jobs"
[hideThumbnail]="true"
[item]="item"
appearance="square"
></stapps-data-list-item>
<ion-item [routerLink]="['/jobs']" class="more-jobs" lines="none">
<ion-label>{{ 'dashboard.jobs.title' | translate | titlecase }}</ion-label>
<ion-icon color="medium" name="read_more" size="40"></ion-icon>
</ion-item>
</simple-swiper>
<ng-template #noItems>
<ion-item class="nothing-selected" lines="none">
<ion-label class="ion-text-wrap">
{{ 'dashboard.jobs.noJobs' | translate }}
</ion-label>
</ion-item>
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/jobs']">
<ion-icon slot="icon-only" name="search" size="24"></ion-icon>
</ion-button>
</ng-template>
</stapps-section>

View File

@@ -0,0 +1,48 @@
/*!
* Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
.nothing-selected::part(native) {
color: var(--ion-color-medium-shade);
background: none;
}
simple-swiper {
--swiper-slide-width: 280px;
}
.more-jobs {
--color: var(--ion-color-medium-tint);
font-size: var(--font-size-xl);
&::part(native) {
height: 100%;
background: none;
border-radius: var(--border-radius-default);
}
ion-label {
position: absolute;
top: 0;
left: 0;
}
ion-icon {
position: absolute;
z-index: 100;
right: 10px;
bottom: 10px;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
import {SCSearchResult, SCThingType} from '@openstapps/core';
import {DataProvider} from 'src/app/modules/data/data.provider';
import {fadeAnimation} from '../../fade.animation';
/**
* Shows a section with meals of the chosen mensa
*/
@Component({
selector: 'stapps-job-section',
templateUrl: 'job-section.component.html',
styleUrls: ['job-section.component.scss'],
animations: [fadeAnimation],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class JobSectionComponent {
jobs = inject(DataProvider)
.search({
filter: {type: 'value', arguments: {field: 'type', value: SCThingType.JobPosting}},
size: 5,
from: 0,
})
.then((result: SCSearchResult) => result.data);
}

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCDateSeries, SCThings, SCThingType} from '@openstapps/core';
import {SCDateSeries, SCThingType, SCThings} from '@openstapps/core';
/**
* Shows a horizontal list of action chips
@@ -37,12 +37,20 @@ export class ActionChipListComponent {
@Input() set item(item: SCThings) {
this._item = item;
const isInPlace = 'inPlace' in item && !!item.inPlace && 'geo' in item.inPlace;
const hasDirectGeo = 'geo' in item;
const maybeCoords = isInPlace
? item?.inPlace?.geo.point.coordinates
: hasDirectGeo
? item.geo.point.coordinates
: undefined;
const isNullIsland = maybeCoords ? maybeCoords[0] === 0 && maybeCoords[1] === 0 : false;
this.applicable = {
locate: false, // TODO: reimplement this at a later date
event:
item.type === SCThingType.AcademicEvent ||
(item.type === SCThingType.DateSeries && (item as SCDateSeries).dates.length > 0),
navigate: ('inPlace' in item && item.inPlace && 'geo' in item.inPlace) || 'geo' in item,
navigate: (hasDirectGeo || isInPlace) && !isNullIsland,
};
}

View File

@@ -46,4 +46,5 @@ export const DataIcons: Record<SCThingType, string> = {
'tour': SCIcon`tour`,
'video': SCIcon`movie`,
'diff': SCIcon`difference`,
'job posting': SCIcon`work`,
};

View File

@@ -17,93 +17,95 @@ import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {LeafletModule} from '@asymmetrik/ngx-leaflet';
import {IonicModule, Platform} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
import {MarkdownModule} from 'ngx-markdown';
import {MomentModule} from 'ngx-moment';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {MenuModule} from '../menu/menu.module';
import {SimpleBrowser, browserFactory} from '../../util/browser.factory';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {RoutingStackService} from '../../util/routing-stack.service';
import {UtilModule} from '../../util/util.module';
import {CalendarService} from '../calendar/calendar.service';
import {ScheduleProvider} from '../calendar/schedule.provider';
import {GeoNavigationDirective} from '../map/geo-navigation.directive';
import {MapWidgetComponent} from '../map/widget/map-widget.component';
import {MenuModule} from '../menu/menu.module';
import {SettingsProvider} from '../settings/settings.provider';
import {StorageModule} from '../storage/storage.module';
import {ActionChipListComponent} from './chips/action-chip-list.component';
import {EditEventSelectionComponent} from './chips/edit-event-selection.component';
import {AddEventActionChipComponent} from './chips/data/add-event-action-chip.component';
import {LocateActionChipComponent} from './chips/data/locate-action-chip.component';
import {NavigateActionChipComponent} from './chips/data/navigate-action-chip.component';
import {EditEventSelectionComponent} from './chips/edit-event-selection.component';
import {CoordinatedSearchProvider} from './coordinated-search.provider';
import {DataFacetsProvider} from './data-facets.provider';
import {DataIconPipe} from './data-icon.pipe';
import {DataRoutingModule} from './data-routing.module';
import {DataProvider} from './data.provider';
import {DataDetailContentComponent} from './detail/data-detail-content.component';
import {DataDetailComponent} from './detail/data-detail.component';
import {DataPathComponent} from './detail/data-path.component';
import {AddressDetailComponent} from './elements/address-detail.component';
import {CertificationsInDetailComponent} from './elements/certifications-in-detail.component';
import {ExternalLinkComponent} from './elements/external-link.component';
import {FavoriteButtonComponent} from './elements/favorite-button.component';
import {LongInlineTextComponent} from './elements/long-inline-text.component';
import {OffersDetailComponent} from './elements/offers-detail.component';
import {OffersInListComponent} from './elements/offers-in-list.component';
import {OriginDetailComponent} from './elements/origin-detail.component';
import {OriginInListComponent} from './elements/origin-in-list.component';
import {StappsRatingComponent} from './elements/rating.component';
import {SimpleCardComponent} from './elements/simple-card.component';
import {SkeletonListItemComponent} from './elements/skeleton-list-item.component';
import {SkeletonSegmentComponent} from './elements/skeleton-segment-button.component';
import {SkeletonSimpleCardComponent} from './elements/skeleton-simple-card.component';
import {TitleCardComponent} from './elements/title-card.component';
import {DataListItemHostDefaultComponent} from './list/data-list-item-host-default.component';
import {DataListItemHostDirective} from './list/data-list-item-host.directive';
import {DataListItemComponent} from './list/data-list-item.component';
import {DataListComponent} from './list/data-list.component';
import {FoodDataListComponent} from './list/food-data-list.component';
import {SearchPageComponent} from './list/search-page.component';
import {SimpleDataListComponent} from './list/simple-data-list.component';
import {SkeletonListComponent} from './list/skeleton-list.component';
import {TreeListFragmentComponent} from './list/tree-list-fragment.component';
import {TreeListComponent} from './list/tree-list.component';
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
import {ArticleContentComponent} from './types/article/article-content.component';
import {ArticleListItemComponent} from './types/article/article-item.component';
import {BookDetailContentComponent} from './types/book/book-detail-content.component';
import {BookListItemComponent} from './types/book/book-list-item.component';
import {CatalogDetailContentComponent} from './types/catalog/catalog-detail-content.component';
import {CatalogListItemComponent} from './types/catalog/catalog-list-item.component';
import {DateSeriesDetailContentComponent} from './types/date-series/date-series-detail-content.component';
import {DateSeriesListItemComponent} from './types/date-series/date-series-list-item.component';
import {DishCharacteristicsComponent} from './types/dish/dish-characteristics.component';
import {DishDetailContentComponent} from './types/dish/dish-detail-content.component';
import {DishListItemComponent} from './types/dish/dish-list-item.component';
import {EventDetailContentComponent} from './types/event/event-detail-content.component';
import {EventListItemComponent} from './types/event/event-list-item.component';
import {EventRoutePathComponent} from './types/event/event-route-path.component';
import {FavoriteDetailContentComponent} from './types/favorite/favorite-detail-content.component';
import {FavoriteListItemComponent} from './types/favorite/favorite-list-item.component';
import {JobPostingDetailContentComponent} from './types/job-posting/job-posting-detail-content.component';
import {JobPostingListItemComponent} from './types/job-posting/job-posting-list-item.component';
import {MessageDetailContentComponent} from './types/message/message-detail-content.component';
import {MessageListItemComponent} from './types/message/message-list-item.component';
import {OrganizationDetailContentComponent} from './types/organization/organization-detail-content.component';
import {OrganizationListItemComponent} from './types/organization/organization-list-item.component';
import {PeriodicalDetailContentComponent} from './types/periodical/periodical-detail-content.component';
import {PeriodicalListItemComponent} from './types/periodical/periodical-list-item.component';
import {PersonDetailContentComponent} from './types/person/person-detail-content.component';
import {PersonListItemComponent} from './types/person/person-list-item.component';
import {PlaceDetailContentComponent} from './types/place/place-detail-content.component';
import {PlaceListItemComponent} from './types/place/place-list-item.component';
import {PlaceMensaDetailComponent} from './types/place/special/mensa/place-mensa-detail.component';
import {SemesterDetailContentComponent} from './types/semester/semester-detail-content.component';
import {MapWidgetComponent} from '../map/widget/map-widget.component';
import {LeafletModule} from '@asymmetrik/ngx-leaflet';
import {SkeletonSimpleCardComponent} from './elements/skeleton-simple-card.component';
import {CatalogListItemComponent} from './types/catalog/catalog-list-item.component';
import {DataListItemComponent} from './list/data-list-item.component';
import {DateSeriesListItemComponent} from './types/date-series/date-series-list-item.component';
import {DishListItemComponent} from './types/dish/dish-list-item.component';
import {FavoriteListItemComponent} from './types/favorite/favorite-list-item.component';
import {LongInlineTextComponent} from './elements/long-inline-text.component';
import {MessageListItemComponent} from './types/message/message-list-item.component';
import {OrganizationListItemComponent} from './types/organization/organization-list-item.component';
import {PersonListItemComponent} from './types/person/person-list-item.component';
import {SkeletonListItemComponent} from './elements/skeleton-list-item.component';
import {SkeletonSegmentComponent} from './elements/skeleton-segment-button.component';
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
import {SemesterListItemComponent} from './types/semester/semester-list-item.component';
import {VideoDetailContentComponent} from './types/video/video-detail-content.component';
import {VideoListItemComponent} from './types/video/video-list-item.component';
import {OriginInListComponent} from './elements/origin-in-list.component';
import {CoordinatedSearchProvider} from './coordinated-search.provider';
import {FavoriteButtonComponent} from './elements/favorite-button.component';
import {SimpleDataListComponent} from './list/simple-data-list.component';
import {TitleCardComponent} from './elements/title-card.component';
import {CalendarService} from '../calendar/calendar.service';
import {RoutingStackService} from '../../util/routing-stack.service';
import {DataPathComponent} from './detail/data-path.component';
import {EventRoutePathComponent} from './types/event/event-route-path.component';
import {UtilModule} from '../../util/util.module';
import {TreeListComponent} from './list/tree-list.component';
import {TreeListFragmentComponent} from './list/tree-list-fragment.component';
import {SettingsProvider} from '../settings/settings.provider';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {ExternalLinkComponent} from './elements/external-link.component';
import {ArticleListItemComponent} from './types/article/article-item.component';
import {ArticleContentComponent} from './types/article/article-content.component';
import {BookDetailContentComponent} from './types/book/book-detail-content.component';
import {BookListItemComponent} from './types/book/book-list-item.component';
import {PeriodicalListItemComponent} from './types/periodical/periodical-list-item.component';
import {PeriodicalDetailContentComponent} from './types/periodical/periodical-detail-content.component';
import {DataListItemHostDirective} from './list/data-list-item-host.directive';
import {DataListItemHostDefaultComponent} from './list/data-list-item-host-default.component';
import {browserFactory, SimpleBrowser} from '../../util/browser.factory';
import {StappsRatingComponent} from './elements/rating.component';
import {DishCharacteristicsComponent} from './types/dish/dish-characteristics.component';
import {SkeletonListComponent} from './list/skeleton-list.component';
import {CertificationsInDetailComponent} from './elements/certifications-in-detail.component';
import {GeoNavigationDirective} from '../map/geo-navigation.directive';
import {NavigateActionChipComponent} from './chips/data/navigate-action-chip.component';
/**
* Module for handling data
@@ -142,6 +144,8 @@ import {NavigateActionChipComponent} from './chips/data/navigate-action-chip.com
MapWidgetComponent,
MessageDetailContentComponent,
MessageListItemComponent,
JobPostingDetailContentComponent,
JobPostingListItemComponent,
OffersDetailComponent,
OffersInListComponent,
OrganizationDetailContentComponent,

View File

@@ -58,6 +58,10 @@
[item]="$any(item)"
*ngSwitchCase="'message'"
></stapps-message-detail-content>
<stapps-job-posting-detail-content
[item]="$any(item)"
*ngSwitchCase="'job posting'"
></stapps-job-posting-detail-content>
<stapps-person-detail-content [item]="$any(item)" *ngSwitchCase="'person'"></stapps-person-detail-content>
<stapps-place-detail-content [item]="$any(item)" *ngSwitchCase="'building'"></stapps-place-detail-content>
<stapps-place-detail-content [item]="$any(item)" *ngSwitchCase="'floor'"></stapps-place-detail-content>

View File

@@ -30,15 +30,17 @@
<div *ngIf="$any(item).openingHours" class="opening-hours">
<stapps-opening-hours [openingHours]="item.openingHours"></stapps-opening-hours>
</div>
<div *ngIf="item.description" class="description">
<!-- TODO obviously this is bad style. Tbd where to put the differentiation. Job Postings always have a description, but it's going to be shown in `stapps-job-posting-detail-content` anyways, no need to repeat here. For this view, I would use other fields of the schema.org JobPosting like the `ThingWithCategory.category` -->
<div *ngIf="item.description && item.type !== 'job posting'" class="description">
<div class="text-accordion" [style.-webkit-line-clamp]="descriptionLinesToDisplay" #accordionTextArea>
{{ 'description' | thingTranslate : item }}
</div>
</div>
<!-- TODO see above -->
<ion-button
expand="full"
fill="clear"
*ngIf="item.description && buttonShown"
*ngIf="item.description && item.type !== 'job posting' && buttonShown"
(click)="toggleDescriptionAccordion()"
>
<ion-icon [name]="buttonState" size="large"></ion-icon>

View File

@@ -29,6 +29,7 @@ import {PeriodicalListItemComponent} from '../types/periodical/periodical-list-i
import {DataListItemHostDefaultComponent} from './data-list-item-host-default.component';
import {ArticleListItemComponent} from '../types/article/article-item.component';
import {DishListItemComponent} from '../types/dish/dish-list-item.component';
import {JobPostingListItemComponent} from '../types/job-posting/job-posting-list-item.component';
export interface DataListItem {
item: SCThings;
@@ -53,6 +54,7 @@ const DataListItemIndex: Partial<Record<SCThingType, Type<DataListItem>>> = {
[SCThingType.Periodical]: PeriodicalListItemComponent,
[SCThingType.Book]: BookListItemComponent,
[SCThingType.Article]: ArticleListItemComponent,
[SCThingType.JobPosting]: JobPostingListItemComponent,
};
@Directive({

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCJobPosting} from '@openstapps/core';
@Component({
selector: 'stapps-job-posting-detail-content',
templateUrl: 'job-posting-detail-content.html',
styleUrls: ['job-posting-detail-content.scss'],
})
export class JobPostingDetailContentComponent {
@Input() item: SCJobPosting;
}

View File

@@ -0,0 +1,42 @@
<!--
~ Copyright (C) 2023 StApps
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-card>
<ion-card-content [innerHtml]="item.description"> </ion-card-content>
</ion-card>
<ion-card *ngIf="item.sameAs">
<ion-card-header> {{ 'sameAs' | propertyNameTranslate : item | titlecase }} </ion-card-header>
<ion-card-content>
<stapps-external-link [link]="item.sameAs" [text]="item.name"></stapps-external-link>
</ion-card-content>
</ion-card>
<ion-card *ngIf="item.employerOverview">
<ion-card-header> {{ 'jobs.employer' | translate }} </ion-card-header>
<ion-card-content>
<stapps-external-link
*ngIf="item.employerOverview.sameAs"
[link]="item.employerOverview.sameAs"
[text]="item.employerOverview.name"
></stapps-external-link>
<p *ngIf="!item.employerOverview.sameAs">{{ item.employerOverview.name }}</p>
<ion-img
*ngIf="item.employerOverview.image"
class="company-image"
src="{{ item.employerOverview.image }}"
></ion-img>
<p [innerHtml]="item.employerOverview.description"></p>
</ion-card-content>
</ion-card>

View File

@@ -0,0 +1,6 @@
.company-image {
width: 200px;
height: 100px;
margin: var(--spacing-md) 0 var(--spacing-md) 0;
object-position: left;
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {SCJobPosting} from '@openstapps/core';
import {DataListItemComponent} from '../../list/data-list-item.component';
@Component({
selector: 'stapps-job-posting-list-item',
templateUrl: 'job-posting-list-item.html',
styleUrls: ['job-posting-list-item.scss'],
})
export class JobPostingListItemComponent extends DataListItemComponent {
@Input() item: SCJobPosting;
}

View File

@@ -0,0 +1,26 @@
<!--
~ Copyright (C) 2022 StApps
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-grid>
<ion-row>
<ion-col>
<ion-label class="title">{{ 'name' | thingTranslate: item }}</ion-label>
<ion-label class="title-sub categories">
{{ 'categories' | thingTranslate: item | join: ', ' | titlecase }}
</ion-label>
<ion-label class="employer">{{ item.employerOverview?.name }}</ion-label>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,13 @@
// since we have three lines of information, clamp harder than usual
ion-col ion-label {
-webkit-line-clamp: 2;
&.title-sub,
&.employer {
-webkit-line-clamp: 1 !important;
}
}
ion-label.categories {
color: var(--ion-color-medium);
}

View File

@@ -0,0 +1,31 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {IonicModule} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
import {MomentModule} from 'ngx-moment';
import {DataModule} from '../data/data.module';
import {UtilModule} from '../../util/util.module';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {ConfigProvider} from '../config/config.provider';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
import {RouterModule, Routes} from '@angular/router';
import {JobsPageComponent} from './page/jobs-page.component';
const jobsRoutes: Routes = [{path: 'jobs', component: JobsPageComponent}];
@NgModule({
declarations: [JobsPageComponent],
imports: [
IonicModule.forRoot(),
ThingTranslateModule.forChild(),
TranslateModule.forChild(),
RouterModule.forChild(jobsRoutes),
IonIconModule,
CommonModule,
MomentModule,
DataModule,
UtilModule,
],
providers: [ConfigProvider],
})
export class JobModule {}

View File

@@ -0,0 +1,17 @@
import {Component} from '@angular/core';
import {SCSearchFilter, SCThingType} from '@openstapps/core';
@Component({
selector: 'stapps-jobs-page',
templateUrl: 'jobs-page.html',
styleUrls: ['jobs-page.scss'],
})
export class JobsPageComponent {
forcedFilter: SCSearchFilter = {
type: 'value',
arguments: {
field: 'type',
value: SCThingType.JobPosting,
},
};
}

View File

@@ -0,0 +1,7 @@
<stapps-search-page
[title]="'jobs.title' | translate"
[placeholder]="'jobs.placeholder' | translate"
[showDefaultData]="true"
[forcedFilter]="forcedFilter"
[backUrl]="'/'"
></stapps-search-page>

View File

@@ -20,7 +20,7 @@
</a>
<ng-template #titleTemplate>
<ion-label class="section-headline">{{ title }} </ion-label>
<ion-label class="section-headline">{{ title }}</ion-label>
<ng-content select="[slot=subtitle]"></ng-content>
</ng-template>
</ion-col>

View File

@@ -98,6 +98,10 @@
"title": "Aktuelles",
"moreNews": "Mehr Nachrichten"
},
"jobs": {
"title": "Jobangebote",
"noJobs": "Es sind momentan keine Jobangebote verfügbar."
},
"schedule": {
"title": "Nächste Einheit",
"noEvent": "Kein Eintrag gefunden",
@@ -390,6 +394,11 @@
"news": {
"title": "Aktuelles"
},
"jobs": {
"title": "Jobangebote",
"placeholder": "Jobangebote",
"employer": "Arbeitgeber"
},
"canteens": {
"title": "Mensa"
},

View File

@@ -98,6 +98,10 @@
"title": "News",
"moreNews": "More News"
},
"jobs": {
"title": "Job postings",
"noJobs": "At the moment, there are not job postings available."
},
"schedule": {
"title": "Next Unit",
"noEvent": "No entry found",
@@ -390,6 +394,11 @@
"news": {
"title": "News"
},
"jobs": {
"title": "Job postings",
"placeholder": "Job Postings",
"employer": "Employer"
},
"canteens": {
"title": "Canteens"
},