From 2cfb23202f150b127f1fea97e4db21ed6942870b Mon Sep 17 00:00:00 2001 From: rbento1096 Date: Wed, 28 Feb 2024 00:33:45 +0000 Subject: [PATCH] feat: Added Venus front-end #78 #104 --- .../src/app/tabs/rooms/roomCard.component.ts | 72 +++++++++ front-end/src/app/tabs/rooms/rooms.service.ts | 8 +- front-end/src/app/tabs/tabs.routing.module.ts | 4 + front-end/src/app/tabs/venues/venue.page.html | 36 +++++ front-end/src/app/tabs/venues/venue.page.scss | 0 front-end/src/app/tabs/venues/venue.page.ts | 51 +++++++ .../app/tabs/venues/venueCard.component.ts | 64 ++++++++ .../app/tabs/venues/venues-routing.module.ts | 16 ++ .../src/app/tabs/venues/venues.module.ts | 25 +++ .../src/app/tabs/venues/venues.page.html | 69 +++++++++ .../src/app/tabs/venues/venues.page.scss | 0 front-end/src/app/tabs/venues/venues.page.ts | 67 ++++++++ front-end/src/assets/i18n/en.json | 7 + front-end/src/global.scss | 143 ++++++++++++++++++ 14 files changed, 559 insertions(+), 3 deletions(-) create mode 100644 front-end/src/app/tabs/rooms/roomCard.component.ts create mode 100644 front-end/src/app/tabs/venues/venue.page.html create mode 100644 front-end/src/app/tabs/venues/venue.page.scss create mode 100644 front-end/src/app/tabs/venues/venue.page.ts create mode 100644 front-end/src/app/tabs/venues/venueCard.component.ts create mode 100644 front-end/src/app/tabs/venues/venues-routing.module.ts create mode 100644 front-end/src/app/tabs/venues/venues.module.ts create mode 100644 front-end/src/app/tabs/venues/venues.page.html create mode 100644 front-end/src/app/tabs/venues/venues.page.scss create mode 100644 front-end/src/app/tabs/venues/venues.page.ts diff --git a/front-end/src/app/tabs/rooms/roomCard.component.ts b/front-end/src/app/tabs/rooms/roomCard.component.ts new file mode 100644 index 0000000..bc3077a --- /dev/null +++ b/front-end/src/app/tabs/rooms/roomCard.component.ts @@ -0,0 +1,72 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +import { IDEATranslationsModule } from '@idea-ionic/common'; + +import { AppService } from 'src/app/app.service'; + +import { Room } from '@models/room.model'; + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule, IonicModule, IDEATranslationsModule], + selector: 'app-room-card', + template: ` + + + + {{ room.name }} + {{ room.internalLocation }} + + + + + + + + + + + {{ room.venue.name }} + + + + {{ room.name }} + {{ room.internalLocation }} + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + ` +}) +export class RoomCardStandaloneComponent { + @Input() room: Room; + @Input() preview: boolean; + + constructor(public app: AppService) {} +} diff --git a/front-end/src/app/tabs/rooms/rooms.service.ts b/front-end/src/app/tabs/rooms/rooms.service.ts index 30f3090..6112bda 100644 --- a/front-end/src/app/tabs/rooms/rooms.service.ts +++ b/front-end/src/app/tabs/rooms/rooms.service.ts @@ -14,22 +14,24 @@ export class RoomsService { constructor(private api: IDEAApiService) {} - private async loadList(): Promise { - this.rooms = (await this.api.getResource(['rooms'])).map(r => new Room(r)); + private async loadList(venue?: string): Promise { + this.rooms = (await this.api.getResource(['rooms'], { params: { venue } })).map(r => new Room(r)); } /** * Get (and optionally filter) the list of rooms. * Note: it can be paginated. * Note: it's a slice of the array. + * Note: if venue id is passed, it will filter rooms for that venue. */ async getList(options: { force?: boolean; withPagination?: boolean; startPaginationAfterId?: string; search?: string; + venue?: string; }): Promise { - if (!this.rooms || options.force) await this.loadList(); + if (!this.rooms || options.force) await this.loadList(options.venue); if (!this.rooms) return null; options.search = options.search ? String(options.search).toLowerCase() : ''; diff --git a/front-end/src/app/tabs/tabs.routing.module.ts b/front-end/src/app/tabs/tabs.routing.module.ts index 2e553f9..521b1e6 100644 --- a/front-end/src/app/tabs/tabs.routing.module.ts +++ b/front-end/src/app/tabs/tabs.routing.module.ts @@ -16,6 +16,10 @@ const routes: Routes = [ { path: 'manage', loadChildren: (): Promise => import('./manage/manage.module').then(m => m.ManageModule) + }, + { + path: 'venues', + loadChildren: (): Promise => import('./venues/venues.module').then(m => m.VenuesModule) } ] } diff --git a/front-end/src/app/tabs/venues/venue.page.html b/front-end/src/app/tabs/venues/venue.page.html new file mode 100644 index 0000000..77bc319 --- /dev/null +++ b/front-end/src/app/tabs/venues/venue.page.html @@ -0,0 +1,36 @@ + + + {{ 'VENUES.DETAILS' | translate }} + + + + + + + + + + + + + +

{{ 'VENUES.ROOMS' | translate }}

+
+
+ + + + + + + {{ 'COMMON.NO_ELEMENT_FOUND' | translate }} + + + + + +
+
+
+
+
diff --git a/front-end/src/app/tabs/venues/venue.page.scss b/front-end/src/app/tabs/venues/venue.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/front-end/src/app/tabs/venues/venue.page.ts b/front-end/src/app/tabs/venues/venue.page.ts new file mode 100644 index 0000000..9f9d2bf --- /dev/null +++ b/front-end/src/app/tabs/venues/venue.page.ts @@ -0,0 +1,51 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { IDEALoadingService, IDEAMessageService } from '@idea-ionic/common'; + +import { AppService } from 'src/app/app.service'; +import { VenuesService } from './venues.service'; +import { RoomsService } from '../rooms/rooms.service'; + +import { Room } from '@models/room.model'; +import { Venue } from '@models/venue.model'; + +@Component({ + selector: 'app-venue', + templateUrl: './venue.page.html', + styleUrls: ['./venue.page.scss'] +}) +export class VenuePage implements OnInit { + venue: Venue; + rooms: Room[]; + + constructor( + private route: ActivatedRoute, + private loading: IDEALoadingService, + private message: IDEAMessageService, + private _venues: VenuesService, + private _rooms: RoomsService, + public app: AppService + ) {} + + async ngOnInit() { + await this.loadData(); + } + + async loadData() { + try { + await this.loading.show(); + const venueId = this.route.snapshot.paramMap.get('venueId'); + this.venue = await this._venues.getById(venueId); + this.rooms = await this._rooms.getList({ venue: this.venue.venueId, force: true }); + } catch (err) { + this.message.error('COMMON.NOT_FOUND'); + } finally { + await this.loading.hide(); + } + } + + async filterRooms(search: string = ''): Promise { + this.rooms = await this._rooms.getList({ search, venue: this.venue.venueId }); + } +} diff --git a/front-end/src/app/tabs/venues/venueCard.component.ts b/front-end/src/app/tabs/venues/venueCard.component.ts new file mode 100644 index 0000000..9ddc420 --- /dev/null +++ b/front-end/src/app/tabs/venues/venueCard.component.ts @@ -0,0 +1,64 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +import { IDEATranslationsModule } from '@idea-ionic/common'; + +import { AppService } from 'src/app/app.service'; + +import { Venue } from '@models/venue.model'; + +@Component({ + standalone: true, + imports: [CommonModule, FormsModule, IonicModule, IDEATranslationsModule], + selector: 'app-venue-card', + template: ` + + + + {{ venue.name }} + {{ venue.address }} + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + ` +}) +export class VenueCardStandaloneComponent { + @Input() venue: Venue; + + constructor(public app: AppService) {} + + openMap(latitude: number, longitude: number): void { + return; // @todo add map #61 + // openGeoLocationInMap(latitude, longitude); + } +} diff --git a/front-end/src/app/tabs/venues/venues-routing.module.ts b/front-end/src/app/tabs/venues/venues-routing.module.ts new file mode 100644 index 0000000..d3450d3 --- /dev/null +++ b/front-end/src/app/tabs/venues/venues-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { VenuesPage } from './venues.page'; +import { VenuePage } from './venue.page'; + +const routes: Routes = [ + { path: '', component: VenuesPage }, + { path: ':venueId', component: VenuePage } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class VenuesRoutingModule {} diff --git a/front-end/src/app/tabs/venues/venues.module.ts b/front-end/src/app/tabs/venues/venues.module.ts new file mode 100644 index 0000000..2e86be5 --- /dev/null +++ b/front-end/src/app/tabs/venues/venues.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { IDEATranslationsModule } from '@idea-ionic/common'; + +import { VenuesRoutingModule } from './venues-routing.module'; +import { VenuesPage } from './venues.page'; +import { VenuePage } from './venue.page'; +import { VenueCardStandaloneComponent } from './venueCard.component'; +import { RoomCardStandaloneComponent } from '../rooms/roomCard.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + IDEATranslationsModule, + VenuesRoutingModule, + VenueCardStandaloneComponent, + RoomCardStandaloneComponent + ], + declarations: [VenuesPage, VenuePage] +}) +export class VenuesModule {} diff --git a/front-end/src/app/tabs/venues/venues.page.html b/front-end/src/app/tabs/venues/venues.page.html new file mode 100644 index 0000000..aaaa31f --- /dev/null +++ b/front-end/src/app/tabs/venues/venues.page.html @@ -0,0 +1,69 @@ + + + + + {{ 'VENUES.MAP' | translate }} + + + {{ 'VENUES.LIST' | translate }} + + + + + + +
+
+
+ + + + {{ 'COMMON.NO_ELEMENT_FOUND' | translate }} + + + + + + {{ venue.name }} + + +
+
+ +
+
+
+ +
+ + + + {{ 'COMMON.NO_ELEMENT_FOUND' | translate }} + + + + + + {{ venue.name }} + + +
+
+ +
+
+
+
+
diff --git a/front-end/src/app/tabs/venues/venues.page.scss b/front-end/src/app/tabs/venues/venues.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/front-end/src/app/tabs/venues/venues.page.ts b/front-end/src/app/tabs/venues/venues.page.ts new file mode 100644 index 0000000..c37e0d5 --- /dev/null +++ b/front-end/src/app/tabs/venues/venues.page.ts @@ -0,0 +1,67 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { IonContent, IonInfiniteScroll } from '@ionic/angular'; + +// import { openGeoLocationInMap } from '../utils'; // @todo add map #61 this method is in react branch + +import { Venue } from '@models/venue.model'; +import { VenuesService } from './venues.service'; +import { AppService } from 'src/app/app.service'; +import { IDEALoadingService, IDEAMessageService } from '@idea-ionic/common'; + +@Component({ + selector: 'app-venues', + templateUrl: './venues.page.html', + styleUrls: ['./venues.page.scss'] +}) +export class VenuesPage implements OnInit { + @ViewChild(IonContent) content: IonContent; + + segment = 'map'; // @todo add map #61 + venues: Venue[]; + filteredVenues: Venue[]; + mapRef: any; // @todo add map #61 + + constructor( + private loading: IDEALoadingService, + private message: IDEAMessageService, + private _venues: VenuesService, + public app: AppService + ) {} + + ngOnInit() { + this.loadData(); + } + + async loadData() { + try { + await this.loading.show(); + this.venues = await this._venues.getList({}); + } catch (error) { + this.message.error('COMMON.OPERATION_FAILED'); + } finally { + this.loading.hide(); + } + } + + async filter(search = ''): Promise { + this.venues = await this._venues.getList({ search }); + } + + selectVenue(venue: Venue) { + if (venue.venueId === 'home') return; + + this.app.goToInTabs(['venues', venue.venueId]); + + // @todo add map #61 + // if (isMobileMode()) this.app.goToInTabs(['venue', venue.venueId]) + // else { + // this.mapRef.selectVenue(venue); + // } + } + + // @todo add map #61 + // navigateToVenue(venue: Venue, event: Event) { + // event.stopPropagation(); + // openGeoLocationInMap(venue.latitude, venue.longitude); + // } +} diff --git a/front-end/src/assets/i18n/en.json b/front-end/src/assets/i18n/en.json index ce47ff5..cf24ebb 100644 --- a/front-end/src/assets/i18n/en.json +++ b/front-end/src/assets/i18n/en.json @@ -247,6 +247,7 @@ "SPEAKERS": "Speakers", "ORGANIZATIONS": "Organisations", "SESSIONS": "Sessions", + "VENUES": "Venues", "REGISTRATIONS_OPTIONS": "Registrations options", "REGISTRATIONS_OPTIONS_I": "Open/close the registrations and other options.", "ALLOW_REGISTRATIONS": "Allow registrations", @@ -326,5 +327,11 @@ "DISCLAIMER_I": "In order for your spot to be paid for and subsequently validated by the organization, we kindly ask you to save the reference below and insert it, as it is, on the corresponding field in the Stripe payment platform. Price may increase to cover for Stripe's payment processing fees.", "CONFIRM_READ": "I have read and understood", "PROCEED_TO_STRIPE": "Go to Stripe" + }, + "VENUES": { + "LIST": "List", + "MAP": "Map", + "DETAILS": "Venue's details", + "ROOMS": "Venue's rooms" } } diff --git a/front-end/src/global.scss b/front-end/src/global.scss index bf1d182..d112e30 100644 --- a/front-end/src/global.scss +++ b/front-end/src/global.scss @@ -156,6 +156,10 @@ ion-item { max-width: 150px !important; } +ion-item.noElements ion-label { + font-style: italic; +} + // // DATATABLE // @@ -268,3 +272,142 @@ ion-item { line-height: 1; } } + +.flexBox { + text-align: center; + position: absolute; + left: 0; + right: 0; + top: 50%; + transform: translateY(-50%); +} + +ion-toolbar ion-title { + text-align: center !important; + padding: 0 !important; +} + +ion-tab-button.tab-selected ion-label { + font-weight: bold !important; +} + +ion-list { + background: transparent !important; +} +ion-list-header { + padding-left: 0; +} +ion-item ion-label p { + color: var(--ion-color-step-600) !important; +} +ion-item ion-label p a { + color: var(--ion-color-step-450) !important; +} +ion-item-divider { + border-bottom: none !important; + margin-top: 10px !important; + font-weight: 500 !important; + color: var(--ion-color-step-650) !important; +} + +ion-searchbar { + --box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px !important; +} + +ion-card { + box-shadow: 0 0 5px 3px rgba(0, 0, 0, 0.05) !important; +} + +ion-button.button-solid { + --box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px !important; +} + +ion-segment-button { + text-transform: none !important; +} + +ion-label h1 { + font-size: 1.6rem !important; +} +ion-label h2 { + font-size: 1.4rem !important; +} +ion-label h3 { + font-size: 1.2rem !important; +} + +.maplibregl-popup-content { + background: var(--ion-color-white); +} + +.fieldHasError { + border-bottom: 2px solid var(--ion-color-danger); +} + +ion-grid.contentGrid { + max-width: 1200px; + margin: 0 auto; +} + +.rc-md-editor { + background-color: var(--ion-color-white); + border: none; + border-bottom: 1px solid var(--ion-border-color); +} +.rc-md-editor .rc-md-navigation, +.rc-md-editor .editor-container .sec-md .input { + background: transparent; + border-bottom-color: var(--ion-border-color); + color: var(--ion-text-color); +} +.rc-md-editor .rc-md-navigation { + padding: 0 16px; +} +.rc-md-editor .rc-md-navigation .button-wrap .button { + color: var(--ion-color-white-contrast); +} +.rc-md-editor .rc-md-navigation .button-wrap .button:hover { + color: rgba(var(--ion-color-dark-rgb, 0.6)); +} +.custom-html-style { + color: var(--ion-text-color); +} + +.divDescription { + padding: 20px; + background-color: var(--ion-color-light); +} +.divDescription p { + margin-bottom: 16px; + text-align: justify; +} + +.widePopover { + --min-width: 300px; +} +@media only screen and (min-width: 350px) { + .widePopover { + --min-width: 350px; + } +} +@media only screen and (min-width: 400px) { + .widePopover { + --min-width: 400px; + } +} + +.tappable { + cursor: pointer !important; +} + +ion-img.inGallery { + --box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px !important; +} +ion-img.inGallery::part(image) { + border-radius: 2px; +} + +.forceMargins p { + margin: 15px 10px !important; +} +