diff --git a/.changeset/few-pots-clean.md b/.changeset/few-pots-clean.md new file mode 100644 index 00000000..82fd9de3 --- /dev/null +++ b/.changeset/few-pots-clean.md @@ -0,0 +1,7 @@ +--- +'@openstapps/backend': minor +'@openstapps/core': minor +'@openstapps/app': minor +--- + +Add job portal feature diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47692d0d..ee6285f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,13 +37,13 @@ Adding new types requires changes at multiple locations for it to work correctly - Add your SCThing and SCThingWithoutReferences to `src/things/your-thing-name.ts` and make them extend `SCThingWithoutReferences` and `SCThing` respectively - Add your SCThingMeta to `src/things/your-thing-name.ts` and make it extend `SCThingMeta` - Add your SCThingMeta to `SCClasses` in `src/meta.ts` -- Add your SCThing to `SCThingsWithoutDiff` in `src/meta.ts` +- Add your SCThing to `SCIndexableThings ` in `src/meta.ts` - Add your SCThingWithoutReferences to `SCAssociatedThingWithoutReferences` in `src/meta.ts` - Add your SCThing to `SCAssociatedThing` in `src/meta.ts` - Add your SCThing to the `SCThingType` enum in `src/things/abstract/thing.ts` - Add an example file for your SCThing in `test/resources/YourThingName.json` - Add the following lines for your SCThing in `test/type.spec.ts`: - +- Make sure your SCThing (but not SCThingWithoutReferences!) includes the `@indexable` and `@validatable` JSDoc annotations, otherwise neither JSON Schemas nor Elasticsearch mappings will be generated ```typescript /** * Types of properties of SCYourThingName diff --git a/backend/backend/config/default/app/menu.js b/backend/backend/config/default/app/menu.js index 10bf654e..64595513 100644 --- a/backend/backend/config/default/app/menu.js +++ b/backend/backend/config/default/app/menu.js @@ -56,6 +56,19 @@ const menus = [ }, }, }, + { + icon: 'work', + route: '/jobs', + title: 'job postings', + translations: { + de: { + title: 'Jobangebote', + }, + en: { + title: 'job postings', + }, + }, + }, ], title: 'overview', route: '/overview', diff --git a/configuration/backend-config/test/menu.de.xml b/configuration/backend-config/test/menu.de.xml index 14e12782..92ec3111 100644 --- a/configuration/backend-config/test/menu.de.xml +++ b/configuration/backend-config/test/menu.de.xml @@ -5,6 +5,7 @@ + diff --git a/configuration/backend-config/test/menu.xml b/configuration/backend-config/test/menu.xml index 900381d0..e5968a8e 100644 --- a/configuration/backend-config/test/menu.xml +++ b/configuration/backend-config/test/menu.xml @@ -5,6 +5,7 @@ + diff --git a/examples/minimal-connector/src/minimal-connector.ts b/examples/minimal-connector/src/minimal-connector.ts index af42a3cb..ba45e639 100644 --- a/examples/minimal-connector/src/minimal-connector.ts +++ b/examples/minimal-connector/src/minimal-connector.ts @@ -51,7 +51,7 @@ export class MinimalConnector extends Connector { protected async fetchItems(): Promise { return [ { - audiences: ['students', 'employees'], + audiences: ['students', 'employees', 'guests'], categories: [], description: 'Some description 1', messageBody: 'Some message 1', @@ -61,7 +61,7 @@ export class MinimalConnector extends Connector { uid: createUUID({id: 'message_1'}, this.licensePlate), }, { - audiences: ['students', 'employees'], + audiences: ['students', 'employees', 'guests'], categories: [], description: 'Some description 2', messageBody: 'Some message 2', @@ -71,7 +71,7 @@ export class MinimalConnector extends Connector { uid: '', // see Connetor.getItems() }, { - audiences: ['students', 'employees'], + audiences: ['students', 'employees', 'guests'], categories: [], description: 'Some description 3', messageBody: 'Some message 3', diff --git a/frontend/app/.eslintrc.json b/frontend/app/.eslintrc.json index 29a37c94..57d6fbc0 100644 --- a/frontend/app/.eslintrc.json +++ b/frontend/app/.eslintrc.json @@ -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", diff --git a/frontend/app/cypress/integration/dashboard.spec.ts b/frontend/app/cypress/integration/dashboard.spec.ts index 1a34638a..262b51a8 100644 --- a/frontend/app/cypress/integration/dashboard.spec.ts +++ b/frontend/app/cypress/integration/dashboard.spec.ts @@ -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'); }); }); diff --git a/frontend/app/icons.config.ts b/frontend/app/icons.config.ts index 0ba30ecc..cf38cecd 100644 --- a/frontend/app/icons.config.ts +++ b/frontend/app/icons.config.ts @@ -35,6 +35,7 @@ const config: IconConfig = { 'settings', 'info', 'rate_review', + 'work', ], }, codePoints: { diff --git a/frontend/app/src/app/app.module.ts b/frontend/app/src/app/app.module.ts index 7f4859b7..4de1b435 100644 --- a/frontend/app/src/app/app.module.ts +++ b/frontend/app/src/app/app.module.ts @@ -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, diff --git a/frontend/app/src/app/modules/auth/auth-helper.service.ts b/frontend/app/src/app/modules/auth/auth-helper.service.ts index 7dfdd726..39994816 100644 --- a/frontend/app/src/app/modules/auth/auth-helper.service.ts +++ b/frontend/app/src/app/modules/auth/auth-helper.service.ts @@ -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) { diff --git a/frontend/app/src/app/modules/dashboard/dashboard.component.html b/frontend/app/src/app/modules/dashboard/dashboard.component.html index b3e9df5d..9c989e1b 100644 --- a/frontend/app/src/app/modules/dashboard/dashboard.component.html +++ b/frontend/app/src/app/modules/dashboard/dashboard.component.html @@ -48,4 +48,5 @@ + diff --git a/frontend/app/src/app/modules/dashboard/dashboard.module.ts b/frontend/app/src/app/modules/dashboard/dashboard.module.ts index e2d8d9a8..a39c0daa 100644 --- a/frontend/app/src/app/modules/dashboard/dashboard.module.ts +++ b/frontend/app/src/app/modules/dashboard/dashboard.module.ts @@ -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], }) diff --git a/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.html b/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.html new file mode 100644 index 00000000..418d9edb --- /dev/null +++ b/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.html @@ -0,0 +1,43 @@ + + + + + + + + + + {{ 'dashboard.jobs.title' | translate | titlecase }} + + + + + + + {{ 'dashboard.jobs.noJobs' | translate }} + + + + + + + + diff --git a/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.scss b/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.scss new file mode 100644 index 00000000..b0b6eaca --- /dev/null +++ b/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.scss @@ -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 . + */ + +.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; + } +} diff --git a/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.ts b/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.ts new file mode 100644 index 00000000..a4778413 --- /dev/null +++ b/frontend/app/src/app/modules/dashboard/sections/jobs-section/job-section.component.ts @@ -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 . + */ +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); +} diff --git a/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts b/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts index 667f631d..5ddd985b 100644 --- a/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts +++ b/frontend/app/src/app/modules/data/chips/action-chip-list.component.ts @@ -13,7 +13,7 @@ * this program. If not, see . */ 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, }; } diff --git a/frontend/app/src/app/modules/data/data-icon.config.ts b/frontend/app/src/app/modules/data/data-icon.config.ts index bfbeb7a0..774c1db9 100644 --- a/frontend/app/src/app/modules/data/data-icon.config.ts +++ b/frontend/app/src/app/modules/data/data-icon.config.ts @@ -46,4 +46,5 @@ export const DataIcons: Record = { 'tour': SCIcon`tour`, 'video': SCIcon`movie`, 'diff': SCIcon`difference`, + 'job posting': SCIcon`work`, }; diff --git a/frontend/app/src/app/modules/data/data.module.ts b/frontend/app/src/app/modules/data/data.module.ts index bdfc0b23..05829062 100644 --- a/frontend/app/src/app/modules/data/data.module.ts +++ b/frontend/app/src/app/modules/data/data.module.ts @@ -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, diff --git a/frontend/app/src/app/modules/data/detail/data-detail-content.html b/frontend/app/src/app/modules/data/detail/data-detail-content.html index e5dc6d08..737e6d72 100644 --- a/frontend/app/src/app/modules/data/detail/data-detail-content.html +++ b/frontend/app/src/app/modules/data/detail/data-detail-content.html @@ -58,6 +58,10 @@ [item]="$any(item)" *ngSwitchCase="'message'" > + diff --git a/frontend/app/src/app/modules/data/elements/title-card.component.html b/frontend/app/src/app/modules/data/elements/title-card.component.html index 9e4701d9..a3e70887 100644 --- a/frontend/app/src/app/modules/data/elements/title-card.component.html +++ b/frontend/app/src/app/modules/data/elements/title-card.component.html @@ -30,15 +30,17 @@
-
+ +
{{ 'description' | thingTranslate : item }}
+ diff --git a/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts b/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts index 9f9ff197..7b839536 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts +++ b/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts @@ -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>> = { [SCThingType.Periodical]: PeriodicalListItemComponent, [SCThingType.Book]: BookListItemComponent, [SCThingType.Article]: ArticleListItemComponent, + [SCThingType.JobPosting]: JobPostingListItemComponent, }; @Directive({ diff --git a/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.component.ts b/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.component.ts new file mode 100644 index 00000000..dc363ee7 --- /dev/null +++ b/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.component.ts @@ -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 . + */ +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; +} diff --git a/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.html b/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.html new file mode 100644 index 00000000..4e916ee1 --- /dev/null +++ b/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.html @@ -0,0 +1,42 @@ + + + + + + + {{ 'sameAs' | propertyNameTranslate : item | titlecase }} + + + + + + + {{ 'jobs.employer' | translate }} + + +

{{ item.employerOverview.name }}

+ +

+
+
diff --git a/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.scss b/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.scss new file mode 100644 index 00000000..56a29dfd --- /dev/null +++ b/frontend/app/src/app/modules/data/types/job-posting/job-posting-detail-content.scss @@ -0,0 +1,6 @@ +.company-image { + width: 200px; + height: 100px; + margin: var(--spacing-md) 0 var(--spacing-md) 0; + object-position: left; +} diff --git a/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.component.ts b/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.component.ts new file mode 100644 index 00000000..d636c9bb --- /dev/null +++ b/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.component.ts @@ -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 . + */ +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; +} diff --git a/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.html b/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.html new file mode 100644 index 00000000..9152576a --- /dev/null +++ b/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.html @@ -0,0 +1,26 @@ + + + + + + {{ 'name' | thingTranslate: item }} + + {{ 'categories' | thingTranslate: item | join: ', ' | titlecase }} + + {{ item.employerOverview?.name }} + + + diff --git a/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.scss b/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.scss new file mode 100644 index 00000000..1062b251 --- /dev/null +++ b/frontend/app/src/app/modules/data/types/job-posting/job-posting-list-item.scss @@ -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); +} diff --git a/frontend/app/src/app/modules/jobs/jobs.module.ts b/frontend/app/src/app/modules/jobs/jobs.module.ts new file mode 100644 index 00000000..90703617 --- /dev/null +++ b/frontend/app/src/app/modules/jobs/jobs.module.ts @@ -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 {} diff --git a/frontend/app/src/app/modules/jobs/page/jobs-page.component.ts b/frontend/app/src/app/modules/jobs/page/jobs-page.component.ts new file mode 100644 index 00000000..259591ae --- /dev/null +++ b/frontend/app/src/app/modules/jobs/page/jobs-page.component.ts @@ -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, + }, + }; +} diff --git a/frontend/app/src/app/modules/jobs/page/jobs-page.html b/frontend/app/src/app/modules/jobs/page/jobs-page.html new file mode 100644 index 00000000..54c04bce --- /dev/null +++ b/frontend/app/src/app/modules/jobs/page/jobs-page.html @@ -0,0 +1,7 @@ + diff --git a/frontend/app/src/app/modules/jobs/page/jobs-page.scss b/frontend/app/src/app/modules/jobs/page/jobs-page.scss new file mode 100644 index 00000000..e69de29b diff --git a/frontend/app/src/app/util/section.html b/frontend/app/src/app/util/section.html index 4316cd15..261f07dc 100644 --- a/frontend/app/src/app/util/section.html +++ b/frontend/app/src/app/util/section.html @@ -20,7 +20,7 @@ - {{ title }} + {{ title }} diff --git a/frontend/app/src/assets/i18n/de.json b/frontend/app/src/assets/i18n/de.json index c7bc5cd2..08a5c98b 100644 --- a/frontend/app/src/assets/i18n/de.json +++ b/frontend/app/src/assets/i18n/de.json @@ -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" }, diff --git a/frontend/app/src/assets/i18n/en.json b/frontend/app/src/assets/i18n/en.json index fe14dddc..552e0ccd 100644 --- a/frontend/app/src/assets/i18n/en.json +++ b/frontend/app/src/assets/i18n/en.json @@ -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" }, diff --git a/frontend/app/src/assets/icons.min.woff2 b/frontend/app/src/assets/icons.min.woff2 index 433d5e16..52a98aba 100644 Binary files a/frontend/app/src/assets/icons.min.woff2 and b/frontend/app/src/assets/icons.min.woff2 differ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 29060dc8..9902c884 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -31,6 +31,7 @@ export * from './things/course-of-study.js'; export * from './things/date-series.js'; export * from './things/diff.js'; export * from './things/dish.js'; +export * from './things/job-posting.js'; export * from './things/favorite.js'; export * from './things/floor.js'; export * from './things/id-card.js'; diff --git a/packages/core/src/meta.ts b/packages/core/src/meta.ts index 4dadc33e..bd00ec44 100644 --- a/packages/core/src/meta.ts +++ b/packages/core/src/meta.ts @@ -39,6 +39,8 @@ import {SCDiff, SCDiffMeta, SCDiffWithoutReferences} from './things/diff.js'; import {SCDish, SCDishMeta, SCDishWithoutReferences} from './things/dish.js'; import {SCFavorite, SCFavoriteMeta, SCFavoriteWithoutReferences} from './things/favorite.js'; import {SCFloor, SCFloorMeta, SCFloorWithoutReferences} from './things/floor.js'; +import {SCIdCard, SCIdCardMeta, SCIdCardWithoutReferences} from './things/id-card.js'; +import {SCJobPosting, SCJobPostingMeta, SCJobPostingWithoutReferences} from './things/job-posting.js'; import {SCMessage, SCMessageMeta, SCMessageWithoutReferences} from './things/message.js'; import {SCOrganization, SCOrganizationMeta, SCOrganizationWithoutReferences} from './things/organization.js'; import {SCPeriodical, SCPeriodicalMeta, SCPeriodicalWithoutReferences} from './things/periodical.js'; @@ -62,7 +64,6 @@ import {SCTicket, SCTicketMeta, SCTicketWithoutReferences} from './things/ticket import {SCToDo, SCToDoMeta, SCToDoWithoutReferences} from './things/todo.js'; import {SCTour, SCTourMeta, SCTourWithoutReferences} from './things/tour.js'; import {SCVideo, SCVideoMeta, SCVideoWithoutReferences} from './things/video.js'; -import {SCIdCard, SCIdCardMeta, SCIdCardWithoutReferences} from './things/id-card.js'; /** * A map of things, from type to meta data @@ -98,6 +99,7 @@ export const SCClasses: {[K in SCThingType]: object} = { 'tour': SCTourMeta, 'video': SCVideoMeta, 'certification': SCCertificationMeta, + 'job posting': SCJobPostingMeta, }; export type SCIndexableThings = @@ -127,7 +129,8 @@ export type SCIndexableThings = | SCTicket | SCToDo | SCTour - | SCVideo; + | SCVideo + | SCJobPosting; /** * An object that exists in the StAppsCore @@ -172,6 +175,8 @@ export type SCAssociatedThingWithoutReferences = THING e ? SCFloorWithoutReferences : THING extends SCIdCard ? SCIdCardWithoutReferences + : THING extends SCJobPosting + ? SCJobPostingWithoutReferences : THING extends SCMessage ? SCMessageWithoutReferences : THING extends SCOrganization @@ -231,6 +236,8 @@ export type SCAssociatedThing = THING extends SCAssessme ? SCDiff : THING extends SCDishWithoutReferences ? SCDish + : THING extends SCJobPostingWithoutReferences + ? SCJobPosting : THING extends SCFavoriteWithoutReferences ? SCFavorite : THING extends SCFloorWithoutReferences diff --git a/packages/core/src/things/abstract/thing.ts b/packages/core/src/things/abstract/thing.ts index 8c8fa059..b88b269d 100644 --- a/packages/core/src/things/abstract/thing.ts +++ b/packages/core/src/things/abstract/thing.ts @@ -53,6 +53,7 @@ export enum SCThingType { ToDo = 'todo', Tour = 'tour', Video = 'video', + JobPosting = 'job posting', } /** diff --git a/packages/core/src/things/job-posting.ts b/packages/core/src/things/job-posting.ts new file mode 100644 index 00000000..30962fe8 --- /dev/null +++ b/packages/core/src/things/job-posting.ts @@ -0,0 +1,108 @@ +import {SCInPlace} from './abstract/place.js'; +import { + SCThingWithCategories, + SCThingWithCategoriesSpecificValues, + SCThingWithCategoriesWithoutReferences, + SCThingWithCategoriesWithoutReferencesMeta, +} from './abstract/thing-with-categories.js'; +import {SCThingMeta, SCThingType} from './abstract/thing.js'; +import {SCOrganizationWithoutReferences} from './organization.js'; + +/** + * categories of a job posting + */ +export type SCJobCategories = + | 'law' + | 'business' + | 'natural' + | 'it' + | 'education' + | 'other' + | 'arts' + | 'social' + | 'engineering' + | 'communication' + | 'medical'; + +export interface SCJobPostingWithoutReferences + extends SCThingWithCategoriesWithoutReferences, + SCInPlace { + /** + * Type of a job posting + */ + type: SCThingType.JobPosting; +} + +/** + * A JobPosting + * @validatable + * @indexable + */ +export interface SCJobPosting + extends SCThingWithCategories, + SCJobPostingWithoutReferences { + /** + * A description of the employer + * @text + */ + employerOverview?: SCOrganizationWithoutReferences; + + /** + * A JobPosting + */ + type: SCThingType.JobPosting; +} + +export class SCJobPostingMeta extends SCThingMeta { + fieldTranslations = { + de: { + ...new SCThingMeta().fieldTranslations.de, + ...new SCThingWithCategoriesWithoutReferencesMeta().fieldTranslations.de, + employerOverview: 'Arbeitgeberübersicht', + }, + en: { + ...new SCThingMeta().fieldTranslations.en, + ...new SCThingWithCategoriesWithoutReferencesMeta().fieldTranslations.en, + employerOverview: 'employer overview', + }, + }; + + fieldValueTranslations = { + de: { + ...new SCThingMeta().fieldValueTranslations.de, + ...new SCThingWithCategoriesWithoutReferencesMeta().fieldValueTranslations.de, + type: 'Job Angebot', + categories: { + law: 'Recht', + business: 'Business', + natural: 'Ökologisch', + it: 'IT', + education: 'Bildung', + other: 'Andere', + arts: 'Künste', + social: 'Sozial', + engineering: 'Ingenieurswesen', + communication: 'Kommunikation', + medical: 'Medizin', + }, + }, + en: { + ...new SCThingMeta().fieldValueTranslations.en, + ...new SCThingWithCategoriesWithoutReferencesMeta().fieldValueTranslations.en, + type: SCThingType.JobPosting, + categories: { + law: 'law', + business: 'business', + natural: 'natural', + it: 'it', + education: 'education', + other: 'other', + arts: 'arts', + social: 'social', + engineering: 'engineering', + communication: 'communication', + medical: 'medical', + }, + }, + }; +} diff --git a/packages/core/test/resources/indexable/JobPosting1.json b/packages/core/test/resources/indexable/JobPosting1.json new file mode 100644 index 00000000..f5bff34b --- /dev/null +++ b/packages/core/test/resources/indexable/JobPosting1.json @@ -0,0 +1,47 @@ +{ + "errorNames": [], + "instance": { + "identifiers": { + "job_id": "14065" + }, + "name": "INEOS European Commercial Graduate Programme - 2024 GRADUATE COMMERCIAL PROGRAMME", + "employerOverview": { + "type": "organization", + "name": "Ineos Manufacturing Deutschland GmbH", + "uid": "af8c1f7c-eebb-5335-ad0f-d2c9201095c5", + "identifiers": { + "org_id": "2751" + } + }, + "inPlace": { + "type": "point of interest", + "uid": "cda90a25-9a91-5021-a383-8490c970ad99", + "name": "Location of job with id 14065", + "geo": { + "point": { + "type": "Point", + "coordinates": [0, 0] + } + }, + "categories": [], + "address": { + "addressCountry": "de", + "addressLocality": "two of our European locations (Germany, France, Belgium, UK)", + "postalCode": "xxxxx", + "streetAddress": "" + } + }, + "sameAs": "https://stellenportal-uni-frankfurt.de/jobdetails/?job=14065", + "description": "

INEOS ist ein weltweit tätiges Unternehmen, das Grundstoffe und Energie fürs tägliche Leben herstellt. Unsere Produkte leisten bereits heute einen unverzichtbaren Beitrag für unsere Gesellschaft, indem sie die nachhaltigsten Optionen für eine Vielzahl gesellschaftlicher Bedürfnisse sind: für die Konservierung von Lebensmitteln, für sauberes Wasser, für den Bau von Windturbinen, für Sonnenkollektoren und andere erneuerbare Technologien, für den Bau leichterer und treibstoffeffizienterer Fahr- und Flugzeuge, für Geräte und Anwendungen in der Medizin, für Kleidung und Ausrüstung sowie für Isolierungen und viele weitere Anwendungen im Industrie- und Privatbereich.

​​​​​​Unsere Kultur ist geprägt von Vielfalt und Chancengleichheit, von flachen Hierarchien, Flexibilität und der bestmöglichen Förderung der Beschäftigten in einem Arbeitsumfeld, das von Kreativität, Leidenschaft und Tatkraft geprägt ist. Transparenz, Verantwortung und Offenheit sind die fundamentalen Bestandteile unserer Unternehmensphilosophie.

Wir sind davon überzeugt, dass jeder Mensch das Potenzial hat, Großes zu leisten. In diesem Geist engagieren wir uns stark in der Welt des Sports und sind stolz darauf, eine Vielzahl von namhaften Teams zu unterstützen.

=================================================================

Werde Teil des INEOS European Commercial Graduate Programme und qualifiziere dich für eine führende Position bei einem der wichtigsten Global Player in Sachen Chemie und Energie. Du magst unabhängiges Denken, Freiheit, Flexibilität und Herausforderungen und möchtest wirklich etwas bewegen – dann ist INEOS das richtige Unternehmen für dich.

Du wechselst in den vier bis fünf Jahren des Programms zwischen verschiedenen Geschäftsbereichen, bist in Schlüsselpositionen eingesetzt, baust dein Netzwerk auf und sammelst Erfahrungen und Kenntnisse für deine Karriere bei INEOS. Wir entwickeln einen individuellen Fahrplan für deine Karriere. Du lernst verschiedene Länder, Sprachen und Kulturen kennen und bewegst dich in wechselnden Geschäftsfeldern und Projekten.

Wir sorgen mit Offenheit und Flexibilität dafür, dass du vom ersten Tag an mit verantwortungsvollen Aufgaben im operativen Geschäft betraut wirst und deine Potenziale voll entfalten kannst. Du hast direkten Kontakt zum Senior Management und profitierst von Mentoring, Trainings, Events und regelmäßigem Feedback.

Bist du bereit, Teil von INEOS zu werden und die Zukunft mitzugestalten?
Bist du ehrgeizig, humorvoll, begeisterungsfähig und entscheidungsfreudig?

 Anforderungen

Du …

  • hast einen sehr guten Studienabschluss (Bachelor oder Master), idealerweise in einem technisch oder naturwissenschaftlich orientierten Fachbereich
  • hast Interesse an kaufmännischen Themen
  • bringst Initiative, Tatkraft und Resilienz mit
  • hast kommunikatives Geschick und Freude im Umgang mit anderen Menschen
  • bist offen für andere Kulturen
  • beherrschst Englisch fließend in Wort und Schrift
  • bist mobil und flexibel während des Programmzeitraums

Wir bieten dir …

  • die Möglichkeit, bereits zu Beginn deiner Karriere Erfahrung zu sammeln, Verantwortung zu tragen und zur Entwicklung des Unternehmens beizutragen
  • neue Herausforderungen und eine hundertprozentig praxisnahe Ausbildung
  • den Einsatz an verschiedenen europäischen Standorten (Belgien, Deutschland, Frankreich, Großbritannien)
  • den Einsatz in Schlüsselbereichen des Unternehmens, zum Beispiel Supply Chain, Logistik, Trading & Shipping, Einkauf, Operations, Business-Development
  • individuelle Betreuung und Unterstützung durch Mentor/innen mit Senior-Position
  • Trainings von soft skills
  • Events (Corporate and Networking-Events nach dem 1. und 3. Jahr sowie die IN.NAM Challenge)
  • Work-Life-Balance
  • Fitnesstrainings und Gesundheitskurse
  • ein dynamisches und attraktives Arbeitsumfeld
  • ein wettbewerbsfähiges Gehalt, Leistungspackages, Corporate Benefits, einen potenziellen Jahresbonus sowie einen leistungsbezogenen Bonus

Are you IN?
Möchtest du mehr über deine möglichen Aufgaben und die unterschiedlichen Positionen der aktuellen Graduates erfahren, dann besuch hier unsere INEOS Graduates-Seite.

Wir können nur Dokumente berücksichtigen, die in unserem Online-Bewerbungssystem hochgeladen wurden. Hast du deinen derzeitigen Wohnsitz  in Großbritannien oder Belgien, dann bewirb dich bitte direkt in diesen Ländern.

", + "type": "job posting", + "uid": "cda90a25-9a91-5021-a383-8490c970ad99", + "origin": { + "indexed": "2023-10-14T17:35:36.717Z", + "name": "job-connector", + "type": "remote", + "url": "https://api.stellenportal-uni-frankfurt.de/job?id=14065" + }, + "categories": ["natural", "other", "engineering"] + }, + "schema": "SCJobPosting" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d16f6748..2f8218f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12314,6 +12314,7 @@ packages: /immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + requiresBuild: true dev: false /immutable@4.3.1: @@ -13421,6 +13422,7 @@ packages: /lie@3.1.1: resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + requiresBuild: true dependencies: immediate: 3.0.6 dev: false