Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added Venus front-end #78 #104 #112

Merged
merged 1 commit into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions front-end/src/app/tabs/rooms/roomCard.component.ts
Original file line number Diff line number Diff line change
@@ -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: `
<ng-container *ngIf="room; else skeletonTemplate">
<ion-card *ngIf="preview" [color]="preview ? 'white' : ''">
<ion-card-header>
<ion-card-title>{{ room.name }}</ion-card-title>
<ion-card-subtitle>{{ room.internalLocation }}</ion-card-subtitle>
</ion-card-header>
</ion-card>

<ion-card *ngIf="!preview" color="white">
<ion-img [src]="app.getImageURLByURI(room.imageURI)"></ion-img>
<ion-card-header>
<ion-card-subtitle style="font-weight: 300;" class="ion-text-right">
<ion-button fill="clear" (click)="app.goToInTabs(['venues', room.venue.venueId])">
<ion-icon name="location-outline"></ion-icon>
<ion-label>
{{ room.venue.name }}
</ion-label>
</ion-button>
</ion-card-subtitle>
<ion-card-title>{{ room.name }}</ion-card-title>
<ion-card-subtitle>{{ room.internalLocation }}</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<div class="divDescription" *ngIf="room.description">
<ion-textarea readonly [rows]="4" [(ngModel)]="room.description"></ion-textarea>
</div>
</ion-card-content>
</ion-card>
</ng-container>

<ng-template #skeletonTemplate>
<ion-card color="white">
<ion-skeleton-text animated style="height: 200px;"></ion-skeleton-text>
<ion-card-header>
<ion-card-title>
<ion-skeleton-text animated style="width: 60%;"></ion-skeleton-text>
</ion-card-title>
<ion-card-subtitle>
<ion-skeleton-text animated style="width: 50%;"></ion-skeleton-text>
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-skeleton-text animated style="width: 80%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 70%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 60%;"></ion-skeleton-text>
</ion-card-content>
</ion-card>
</ng-template>
`
})
export class RoomCardStandaloneComponent {
@Input() room: Room;
@Input() preview: boolean;

constructor(public app: AppService) {}
}
8 changes: 5 additions & 3 deletions front-end/src/app/tabs/rooms/rooms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,24 @@ export class RoomsService {

constructor(private api: IDEAApiService) {}

private async loadList(): Promise<void> {
this.rooms = (await this.api.getResource(['rooms'])).map(r => new Room(r));
private async loadList(venue?: string): Promise<void> {
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<Room[]> {
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() : '';
Expand Down
4 changes: 4 additions & 0 deletions front-end/src/app/tabs/tabs.routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const routes: Routes = [
{
path: 'manage',
loadChildren: (): Promise<any> => import('./manage/manage.module').then(m => m.ManageModule)
},
{
path: 'venues',
loadChildren: (): Promise<any> => import('./venues/venues.module').then(m => m.VenuesModule)
}
]
}
Expand Down
36 changes: 36 additions & 0 deletions front-end/src/app/tabs/venues/venue.page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<ion-header>
<ion-toolbar>
<ion-title>{{ 'VENUES.DETAILS' | translate }}</ion-title>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-grid class="contentGrid">
<ion-row class="ion-justify-content-center">
<ion-col size="12" size-md="6">
<app-venue-card [venue]="venue"></app-venue-card>
</ion-col>
<ion-col *ngIf="rooms?.length" size="12" size-md="6">
<ion-list>
<ion-list-header>
<ion-label class="ion-text-center">
<h2>{{ 'VENUES.ROOMS' | translate }}</h2>
</ion-label>
</ion-list-header>
<ion-searchbar #searchbar color="light" (ionInput)="filterRooms($event.target.value)"></ion-searchbar>
<ion-col *ngIf="!rooms">
<app-room-card [preview]="true"></app-room-card>
</ion-col>
<ion-col *ngIf="rooms && rooms.length === 0">
<ion-item lines="none" color="white">
<ion-label class="ion-text-center">{{ 'COMMON.NO_ELEMENT_FOUND' | translate }}</ion-label>
</ion-item>
</ion-col>
<ion-col *ngFor="let room of rooms">
<app-room-card [room]="room" [preview]="true" (click)="app.goToInTabs(['rooms', room.roomId])"></app-room-card>
</ion-col>
</ion-list>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
Empty file.
51 changes: 51 additions & 0 deletions front-end/src/app/tabs/venues/venue.page.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
this.rooms = await this._rooms.getList({ search, venue: this.venue.venueId });
}
}
64 changes: 64 additions & 0 deletions front-end/src/app/tabs/venues/venueCard.component.ts
Original file line number Diff line number Diff line change
@@ -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: `
<ion-card *ngIf="venue" color="white">
<ion-img [src]="app.getImageURLByURI(venue.imageURI)"></ion-img>
<ion-card-header>
<ion-card-title>{{ venue.name }}</ion-card-title>
<ion-card-subtitle>{{ venue.address }}</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<!-- @todo add map #61 -->
<!-- <div *ngIf="venue.address" class="ion-text-right" style="margin-bottom: 30px;">
<ion-button (click)="openMap(venue.latitude, venue.longitude)">
Navigate to the venue
<ion-icon slot="end" name="navigate"></ion-icon>
</ion-button>
</div> -->
<div class="divDescription" *ngIf="venue.description">
<ion-textarea readonly [rows]="4" [(ngModel)]="venue.description"></ion-textarea>
</div>
</ion-card-content>
</ion-card>

<ion-card *ngIf="!venue" color="white">
<ion-skeleton-text animated style="height: 200px;"></ion-skeleton-text>
<ion-card-header>
<ion-card-title>
<ion-skeleton-text animated style="width: 60%;"></ion-skeleton-text>
</ion-card-title>
<ion-card-subtitle>
<ion-skeleton-text animated style="width: 50%;"></ion-skeleton-text>
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-skeleton-text animated style="width: 80%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 70%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 60%;"></ion-skeleton-text>
</ion-card-content>
</ion-card>
`
})
export class VenueCardStandaloneComponent {
@Input() venue: Venue;

constructor(public app: AppService) {}

openMap(latitude: number, longitude: number): void {
return; // @todo add map #61
// openGeoLocationInMap(latitude, longitude);
}
}
16 changes: 16 additions & 0 deletions front-end/src/app/tabs/venues/venues-routing.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
25 changes: 25 additions & 0 deletions front-end/src/app/tabs/venues/venues.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
69 changes: 69 additions & 0 deletions front-end/src/app/tabs/venues/venues.page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<ion-header>
<ion-toolbar *ngIf="app.isInMobileMode()" color="ideaToolbar">
<ion-segment [(ngModel)]="segment">
<ion-segment-button value="map">
{{ 'VENUES.MAP' | translate }}
</ion-segment-button>
<ion-segment-button value="list">
{{ 'VENUES.LIST' | translate }}
</ion-segment-button>
</ion-segment>
</ion-toolbar>
</ion-header>

<ion-content>
<div *ngIf="venues">
<div *ngIf="app.isInMobileMode(); else desktopContent">
<div [style.display]="segment !== 'list' ? 'none' : 'inherit'">
<ion-list lines="inset" style="padding: 0; max-width: 500px; margin: 0 auto;">
<ion-searchbar #searchbar color="light" (ionInput)="filter($event.target.value)"></ion-searchbar>
<ion-item color="white" class="noElements" *ngIf="venues && !venues.length">
<ion-label>{{ 'COMMON.NO_ELEMENT_FOUND' | translate }}</ion-label>
</ion-item>
<ion-item color="white" *ngIf="!venues">
<ion-label><ion-skeleton-text animated></ion-skeleton-text></ion-label>
</ion-item>
<ion-item
color="white"
*ngFor="let venue of venues"
button
detail
(click)="selectVenue(venue)"
>
<ion-label class="ion-text-wrap">{{ venue.name }}</ion-label>
</ion-item>
</ion-list>
</div>
<div [style.display]="segment !== 'map' ? 'none' : 'inherit'">
<!-- Map View -->
<div id="main-map" #mapRef></div>
</div>
</div>
<ng-template #desktopContent>
<div style="width: 30%; float: left;">
<ion-list lines="inset" style="padding: 0; max-width: 500px; margin: 0 auto;">
<ion-searchbar #searchbar color="light" (ionInput)="filter($event.target.value)"></ion-searchbar>
<ion-item color="white" class="noElements" *ngIf="venues && !venues.length">
<ion-label>{{ 'COMMON.NO_ELEMENT_FOUND' | translate }}</ion-label>
</ion-item>
<ion-item color="white" *ngIf="!venues">
<ion-label><ion-skeleton-text animated></ion-skeleton-text></ion-label>
</ion-item>
<ion-item
color="white"
*ngFor="let venue of venues"
button
detail
(click)="selectVenue(venue)"
>
<ion-label class="ion-text-wrap">{{ venue.name }}</ion-label>
</ion-item>
</ion-list>
</div>
<div style="width: 70%; float: right; right: 0; position: fixed;">
<!-- Desktop Map View -->
<div id="main-map" #mapRef></div>
</div>
</ng-template>
</div>
</ion-content>
Empty file.
Loading