"feat: update data APIs to fetch orders and events via fetch instead of axios, add Order and Event models, update selectors and reducers, add EventDetail page with joined members display"
This commit is contained in:
@@ -4,6 +4,8 @@ import { Speaker } from '../models/Speaker';
|
|||||||
import { Location } from '../models/Location';
|
import { Location } from '../models/Location';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import constants from '../constants';
|
import constants from '../constants';
|
||||||
|
import { IOrderItem } from '../models/Order';
|
||||||
|
import { Event } from '../models/Event';
|
||||||
|
|
||||||
const dataUrl = '/assets/data/data.json';
|
const dataUrl = '/assets/data/data.json';
|
||||||
const locationsUrl = '/assets/data/locations.json';
|
const locationsUrl = '/assets/data/locations.json';
|
||||||
@@ -16,11 +18,13 @@ export const getConfData = async () => {
|
|||||||
const response = await Promise.all([
|
const response = await Promise.all([
|
||||||
fetch(dataUrl),
|
fetch(dataUrl),
|
||||||
fetch(locationsUrl),
|
fetch(locationsUrl),
|
||||||
axios.get(`${constants.API_ENDPOINT}/api/order/list`),
|
fetch(`${constants.API_ENDPOINT}/api/order/list`),
|
||||||
|
fetch(`${constants.API_ENDPOINT}/api/event/list`),
|
||||||
// axios.get(`${constants.API_ENDPOINT}/v1/events`),
|
// axios.get(`${constants.API_ENDPOINT}/v1/events`),
|
||||||
// axios.get(`${constants.API_ENDPOINT}/v1/members`),
|
// axios.get(`${constants.API_ENDPOINT}/v1/members`),
|
||||||
//
|
//
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const responseData = await response[0].json();
|
const responseData = await response[0].json();
|
||||||
const schedule = responseData.schedule[0] as Schedule;
|
const schedule = responseData.schedule[0] as Schedule;
|
||||||
const sessions = parseSessions(schedule);
|
const sessions = parseSessions(schedule);
|
||||||
@@ -33,9 +37,29 @@ export const getConfData = async () => {
|
|||||||
|
|
||||||
// const events = response[2].data;
|
// const events = response[2].data;
|
||||||
// const nearByMembers = response[3].data;
|
// const nearByMembers = response[3].data;
|
||||||
const orders = response[2].data.orders;
|
|
||||||
|
|
||||||
const events = [];
|
// TODO: update this due to not use axios anymore
|
||||||
|
// the data object is not available
|
||||||
|
// const orders = response[2].data.orders as IOrderItem[];
|
||||||
|
// const events = response[3].data.events as Event[];
|
||||||
|
const orderResponse = response[2];
|
||||||
|
let orders = {
|
||||||
|
result: { status: orderResponse.status, ok: orderResponse.ok },
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
if (orderResponse.status == 200) {
|
||||||
|
orders = { ...orders, data: await orderResponse.json() };
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventResponse = response[3];
|
||||||
|
let events = {
|
||||||
|
result: { status: eventResponse.status, ok: eventResponse.ok },
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
if (eventResponse.status == 200) {
|
||||||
|
events = { ...events, data: await eventResponse.json() };
|
||||||
|
}
|
||||||
|
|
||||||
const nearByMembers = [];
|
const nearByMembers = [];
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@@ -47,15 +71,21 @@ export const getConfData = async () => {
|
|||||||
filteredTracks: [...allTracks],
|
filteredTracks: [...allTracks],
|
||||||
//
|
//
|
||||||
events,
|
events,
|
||||||
nearByMembers,
|
// nearByMembers,
|
||||||
orders,
|
orders,
|
||||||
|
hello: 'world',
|
||||||
//
|
//
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserData = async () => {
|
export const getUserData = async () => {
|
||||||
const response = await Promise.all([Storage.get({ key: HAS_LOGGED_IN }), Storage.get({ key: HAS_SEEN_TUTORIAL }), Storage.get({ key: USERNAME })]);
|
const response = await Promise.all([
|
||||||
|
Storage.get({ key: HAS_LOGGED_IN }),
|
||||||
|
Storage.get({ key: HAS_SEEN_TUTORIAL }),
|
||||||
|
Storage.get({ key: USERNAME }),
|
||||||
|
]);
|
||||||
const isLoggedin = (await response[0].value) === 'true';
|
const isLoggedin = (await response[0].value) === 'true';
|
||||||
const hasSeenTutorial = (await response[1].value) === 'true';
|
const hasSeenTutorial = (await response[1].value) === 'true';
|
||||||
const username = (await response[2].value) || undefined;
|
const username = (await response[2].value) || undefined;
|
||||||
|
@@ -2,9 +2,10 @@ import { createSelector } from 'reselect';
|
|||||||
import { Schedule, Session, ScheduleGroup } from '../models/Schedule';
|
import { Schedule, Session, ScheduleGroup } from '../models/Schedule';
|
||||||
import { Speaker } from '../models/Speaker';
|
import { Speaker } from '../models/Speaker';
|
||||||
import { Location } from '../models/Location';
|
import { Location } from '../models/Location';
|
||||||
import { Event } from '../models/Event';
|
|
||||||
|
|
||||||
import { AppState } from './state';
|
import { AppState } from './state';
|
||||||
|
import { IOrderItem } from '../models/Order';
|
||||||
|
import { Event } from '../models/Event';
|
||||||
|
|
||||||
const getSchedule = (state: AppState) => {
|
const getSchedule = (state: AppState) => {
|
||||||
return state.data.schedule;
|
return state.data.schedule;
|
||||||
@@ -17,106 +18,126 @@ const getFilteredTracks = (state: AppState) => state.data.filteredTracks;
|
|||||||
const getFavoriteIds = (state: AppState) => state.data.favorites;
|
const getFavoriteIds = (state: AppState) => state.data.favorites;
|
||||||
const getSearchText = (state: AppState) => state.data.searchText;
|
const getSearchText = (state: AppState) => state.data.searchText;
|
||||||
|
|
||||||
export const getEvents = (state: AppState) => state.data.events;
|
export const getEvents = (state: AppState) => {
|
||||||
|
return state.data.events;
|
||||||
|
};
|
||||||
|
|
||||||
export const getNearbyMembers = (state: AppState) => state.data.nearByMembers;
|
export const getNearbyMembers = (state: AppState) => state.data.nearByMembers;
|
||||||
export const getOrders = (state: AppState) => state.data.orders;
|
|
||||||
|
|
||||||
export const getFilteredSchedule = createSelector(getSchedule, getFilteredTracks, (schedule, filteredTracks) => {
|
export const getOrders = (state: AppState) => {
|
||||||
const groups: ScheduleGroup[] = [];
|
return state.data.orders;
|
||||||
|
};
|
||||||
|
|
||||||
// Helper function to convert 12-hour time to 24-hour time for proper sorting
|
export const getFilteredSchedule = createSelector(
|
||||||
const convertTo24Hour = (timeStr: string) => {
|
getSchedule,
|
||||||
const [time, period] = timeStr.toLowerCase().split(' ');
|
getFilteredTracks,
|
||||||
let [hours, minutes] = time.split(':').map(Number);
|
(schedule, filteredTracks) => {
|
||||||
|
const groups: ScheduleGroup[] = [];
|
||||||
|
|
||||||
if (period === 'pm' && hours !== 12) {
|
// Helper function to convert 12-hour time to 24-hour time for proper sorting
|
||||||
hours += 12;
|
const convertTo24Hour = (timeStr: string) => {
|
||||||
} else if (period === 'am' && hours === 12) {
|
const [time, period] = timeStr.toLowerCase().split(' ');
|
||||||
hours = 0;
|
let [hours, minutes] = time.split(':').map(Number);
|
||||||
}
|
|
||||||
|
|
||||||
return `${hours.toString().padStart(2, '0')}:${minutes || '00'}`;
|
if (period === 'pm' && hours !== 12) {
|
||||||
};
|
hours += 12;
|
||||||
|
} else if (period === 'am' && hours === 12) {
|
||||||
|
hours = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Sort the groups by time
|
return `${hours.toString().padStart(2, '0')}:${minutes || '00'}`;
|
||||||
const sortedGroups = [...schedule.groups].sort((a, b) => {
|
};
|
||||||
const timeA = convertTo24Hour(a.time);
|
|
||||||
const timeB = convertTo24Hour(b.time);
|
|
||||||
return timeA.localeCompare(timeB);
|
|
||||||
});
|
|
||||||
|
|
||||||
sortedGroups.forEach((group: ScheduleGroup) => {
|
// Sort the groups by time
|
||||||
const sessions: Session[] = [];
|
const sortedGroups = [...schedule.groups].sort((a, b) => {
|
||||||
group.sessions.forEach((session) => {
|
const timeA = convertTo24Hour(a.time);
|
||||||
session.tracks.forEach((track) => {
|
const timeB = convertTo24Hour(b.time);
|
||||||
if (filteredTracks.indexOf(track) > -1) {
|
return timeA.localeCompare(timeB);
|
||||||
sessions.push(session);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sessions.length) {
|
sortedGroups.forEach((group: ScheduleGroup) => {
|
||||||
// Sort sessions within each group by start time
|
const sessions: Session[] = [];
|
||||||
const sortedSessions = sessions.sort((a, b) => {
|
group.sessions.forEach((session) => {
|
||||||
const timeA = convertTo24Hour(a.timeStart);
|
session.tracks.forEach((track) => {
|
||||||
const timeB = convertTo24Hour(b.timeStart);
|
if (filteredTracks.indexOf(track) > -1) {
|
||||||
return timeA.localeCompare(timeB);
|
sessions.push(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupToAdd: ScheduleGroup = {
|
if (sessions.length) {
|
||||||
time: group.time,
|
// Sort sessions within each group by start time
|
||||||
sessions: sortedSessions,
|
const sortedSessions = sessions.sort((a, b) => {
|
||||||
};
|
const timeA = convertTo24Hour(a.timeStart);
|
||||||
groups.push(groupToAdd);
|
const timeB = convertTo24Hour(b.timeStart);
|
||||||
}
|
return timeA.localeCompare(timeB);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const groupToAdd: ScheduleGroup = {
|
||||||
date: schedule.date,
|
time: group.time,
|
||||||
groups,
|
sessions: sortedSessions,
|
||||||
} as Schedule;
|
};
|
||||||
});
|
groups.push(groupToAdd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const getSearchedSchedule = createSelector(getFilteredSchedule, getSearchText, (schedule, searchText) => {
|
return {
|
||||||
if (!searchText) {
|
date: schedule.date,
|
||||||
return schedule;
|
groups,
|
||||||
|
} as Schedule;
|
||||||
}
|
}
|
||||||
const groups: ScheduleGroup[] = [];
|
);
|
||||||
schedule.groups.forEach((group) => {
|
|
||||||
const sessions = group.sessions.filter((s) => s.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1);
|
export const getSearchedSchedule = createSelector(
|
||||||
if (sessions.length) {
|
getFilteredSchedule,
|
||||||
const groupToAdd: ScheduleGroup = {
|
getSearchText,
|
||||||
time: group.time,
|
(schedule, searchText) => {
|
||||||
sessions,
|
if (!searchText) {
|
||||||
};
|
return schedule;
|
||||||
groups.push(groupToAdd);
|
|
||||||
}
|
}
|
||||||
});
|
const groups: ScheduleGroup[] = [];
|
||||||
return {
|
schedule.groups.forEach((group) => {
|
||||||
date: schedule.date,
|
const sessions = group.sessions.filter(
|
||||||
groups,
|
(s) => s.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|
||||||
} as Schedule;
|
);
|
||||||
});
|
if (sessions.length) {
|
||||||
|
const groupToAdd: ScheduleGroup = {
|
||||||
|
time: group.time,
|
||||||
|
sessions,
|
||||||
|
};
|
||||||
|
groups.push(groupToAdd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
date: schedule.date,
|
||||||
|
groups,
|
||||||
|
} as Schedule;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const getScheduleList = createSelector(getSearchedSchedule, (schedule) => schedule);
|
export const getScheduleList = createSelector(getSearchedSchedule, (schedule) => schedule);
|
||||||
|
|
||||||
export const getGroupedFavorites = createSelector(getScheduleList, getFavoriteIds, (schedule, favoriteIds) => {
|
export const getGroupedFavorites = createSelector(
|
||||||
const groups: ScheduleGroup[] = [];
|
getScheduleList,
|
||||||
schedule.groups.forEach((group) => {
|
getFavoriteIds,
|
||||||
const sessions = group.sessions.filter((s) => favoriteIds.indexOf(s.id) > -1);
|
(schedule, favoriteIds) => {
|
||||||
if (sessions.length) {
|
const groups: ScheduleGroup[] = [];
|
||||||
const groupToAdd: ScheduleGroup = {
|
schedule.groups.forEach((group) => {
|
||||||
time: group.time,
|
const sessions = group.sessions.filter((s) => favoriteIds.indexOf(s.id) > -1);
|
||||||
sessions,
|
if (sessions.length) {
|
||||||
};
|
const groupToAdd: ScheduleGroup = {
|
||||||
groups.push(groupToAdd);
|
time: group.time,
|
||||||
}
|
sessions,
|
||||||
});
|
};
|
||||||
return {
|
groups.push(groupToAdd);
|
||||||
date: schedule.date,
|
}
|
||||||
groups,
|
});
|
||||||
} as Schedule;
|
return {
|
||||||
});
|
date: schedule.date,
|
||||||
|
groups,
|
||||||
|
} as Schedule;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const getIdParam = (_state: AppState, props: any) => {
|
const getIdParam = (_state: AppState, props: any) => {
|
||||||
return props.match.params['id'];
|
return props.match.params['id'];
|
||||||
@@ -126,9 +147,25 @@ export const getSession = createSelector(getSessions, getIdParam, (sessions, id)
|
|||||||
return sessions.find((s: Session) => s.id === id);
|
return sessions.find((s: Session) => s.id === id);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getSpeaker = createSelector(getSpeakers, getIdParam, (speakers, id) => speakers.find((x: Speaker) => x.id === id));
|
export const getSpeaker = createSelector(getSpeakers, getIdParam, (speakers, id) =>
|
||||||
|
speakers.find((x: Speaker) => x.id === id)
|
||||||
|
);
|
||||||
|
|
||||||
export const getEvent = createSelector(getEvents, getIdParam, (events, id) => events.find((x: Event) => x.id === id));
|
export const getEvent = createSelector(getEvents, getIdParam, (data_events, id) => {
|
||||||
|
const {
|
||||||
|
data: { events },
|
||||||
|
} = data_events;
|
||||||
|
|
||||||
|
return events.find((x: Event) => x.id === id);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getOrder = createSelector(getOrders, getIdParam, (data_orders, id) => {
|
||||||
|
const {
|
||||||
|
data: { orders },
|
||||||
|
} = data_orders;
|
||||||
|
|
||||||
|
return orders.find((x: IOrderItem) => x.id === id);
|
||||||
|
});
|
||||||
|
|
||||||
export const getSpeakerSessions = createSelector(getSessions, (sessions) => {
|
export const getSpeakerSessions = createSelector(getSessions, (sessions) => {
|
||||||
const speakerSessions: { [key: string]: Session[] } = {};
|
const speakerSessions: { [key: string]: Session[] } = {};
|
||||||
|
@@ -3,6 +3,7 @@ import { Speaker } from '../../models/Speaker';
|
|||||||
import { Schedule, Session } from '../../models/Schedule';
|
import { Schedule, Session } from '../../models/Schedule';
|
||||||
//
|
//
|
||||||
import { Event } from '../../models/Event';
|
import { Event } from '../../models/Event';
|
||||||
|
import { IOrderItem } from '../../models/Order';
|
||||||
|
|
||||||
export interface ConfState {
|
export interface ConfState {
|
||||||
schedule: Schedule;
|
schedule: Schedule;
|
||||||
@@ -18,4 +19,5 @@ export interface ConfState {
|
|||||||
menuEnabled: boolean;
|
menuEnabled: boolean;
|
||||||
//
|
//
|
||||||
events: Event[];
|
events: Event[];
|
||||||
|
orders: IOrderItem[];
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
import { SessionsActions } from './sessions.actions';
|
import { SessionsActions } from './sessions.actions';
|
||||||
import { ConfState } from './conf.state';
|
import { ConfState } from './conf.state';
|
||||||
|
|
||||||
export const sessionsReducer = (
|
export const sessionsReducer = (state: ConfState, action: SessionsActions): ConfState => {
|
||||||
state: ConfState,
|
|
||||||
action: SessionsActions
|
|
||||||
): ConfState => {
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'set-conf-loading': {
|
case 'set-conf-loading': {
|
||||||
return { ...state, loading: action.isLoading };
|
return { ...state, loading: action.isLoading };
|
||||||
|
@@ -31,23 +31,21 @@ export const logoutUser = () => async (dispatch: React.Dispatch<any>) => {
|
|||||||
dispatch(setUsername());
|
dispatch(setUsername());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setIsLoggedIn =
|
export const setIsLoggedIn = (loggedIn: boolean) => async (dispatch: React.Dispatch<any>) => {
|
||||||
(loggedIn: boolean) => async (dispatch: React.Dispatch<any>) => {
|
await setIsLoggedInData(loggedIn);
|
||||||
await setIsLoggedInData(loggedIn);
|
return {
|
||||||
return {
|
type: 'set-is-loggedin',
|
||||||
type: 'set-is-loggedin',
|
loggedIn,
|
||||||
loggedIn,
|
} as const;
|
||||||
} as const;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
export const setUsername =
|
export const setUsername = (username?: string) => async (dispatch: React.Dispatch<any>) => {
|
||||||
(username?: string) => async (dispatch: React.Dispatch<any>) => {
|
await setUsernameData(username);
|
||||||
await setUsernameData(username);
|
return {
|
||||||
return {
|
type: 'set-username',
|
||||||
type: 'set-username',
|
username,
|
||||||
username,
|
} as const;
|
||||||
} as const;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
export const setHasSeenTutorial =
|
export const setHasSeenTutorial =
|
||||||
(hasSeenTutorial: boolean) => async (dispatch: React.Dispatch<any>) => {
|
(hasSeenTutorial: boolean) => async (dispatch: React.Dispatch<any>) => {
|
||||||
|
@@ -1,8 +1,19 @@
|
|||||||
|
// 03_source/mobile/src/models/Event.ts
|
||||||
|
|
||||||
|
export type IDateValue = string | number | null;
|
||||||
|
|
||||||
export interface Event {
|
export interface Event {
|
||||||
eventDate: Date;
|
id: string;
|
||||||
joinMembers: undefined;
|
createdAt: IDateValue;
|
||||||
title: string;
|
updatedAt: IDateValue;
|
||||||
|
//
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
price: number;
|
price: number;
|
||||||
|
//
|
||||||
|
eventDate: Date;
|
||||||
|
joinMembers: { email: string; avatar: string; sex: string }[];
|
||||||
|
title: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
duration_m: number;
|
duration_m: number;
|
||||||
ageBottom: number;
|
ageBottom: number;
|
||||||
@@ -10,5 +21,4 @@ export interface Event {
|
|||||||
location: string;
|
location: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
//
|
//
|
||||||
id: string;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,42 @@
|
|||||||
export type IDateValue = string | number | null;
|
export type IDateValue = string | number | null;
|
||||||
|
|
||||||
export interface Order {
|
export type IOrderProductItem = {
|
||||||
|
id: string;
|
||||||
|
sku: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
coverUrl: string;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IOrderHistory = {
|
||||||
|
orderTime: IDateValue;
|
||||||
|
paymentTime: IDateValue;
|
||||||
|
deliveryTime: IDateValue;
|
||||||
|
completionTime: IDateValue;
|
||||||
|
timeline: { title: string; time: IDateValue }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IOrderDelivery = {
|
||||||
|
shipBy: string;
|
||||||
|
speedy: string;
|
||||||
|
trackingNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IOrderShippingAddress = {
|
||||||
|
fullAddress: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IOrderPayment = {
|
||||||
|
cardType: string;
|
||||||
|
cardNumber: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IOrderItem {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: IDateValue;
|
createdAt: IDateValue;
|
||||||
|
updatedAt: IDateValue;
|
||||||
//
|
//
|
||||||
taxes: number;
|
taxes: number;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -12,4 +46,10 @@ export interface Order {
|
|||||||
orderNumber: string;
|
orderNumber: string;
|
||||||
totalAmount: number;
|
totalAmount: number;
|
||||||
totalQuantity: number;
|
totalQuantity: number;
|
||||||
|
//
|
||||||
|
items: IOrderProductItem[];
|
||||||
|
history: IOrderHistory | undefined;
|
||||||
|
delivery: IOrderDelivery;
|
||||||
|
shippingAddress: IOrderShippingAddress;
|
||||||
|
payment: IOrderPayment;
|
||||||
}
|
}
|
||||||
|
134
03_source/mobile/src/pages/EventDetail/AvatarRow.tsx
Normal file
134
03_source/mobile/src/pages/EventDetail/AvatarRow.tsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// REQ0042/event-detail
|
||||||
|
//
|
||||||
|
// PURPOSE:
|
||||||
|
// - show avatar in a row
|
||||||
|
//
|
||||||
|
// RULES:
|
||||||
|
// - T.B.A.
|
||||||
|
//
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonContent,
|
||||||
|
IonPage,
|
||||||
|
IonButtons,
|
||||||
|
IonMenuButton,
|
||||||
|
IonButton,
|
||||||
|
IonIcon,
|
||||||
|
IonDatetime,
|
||||||
|
IonSelectOption,
|
||||||
|
IonList,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonSelect,
|
||||||
|
IonPopover,
|
||||||
|
IonText,
|
||||||
|
IonFooter,
|
||||||
|
useIonRouter,
|
||||||
|
IonAvatar,
|
||||||
|
IonThumbnail,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import './style.scss';
|
||||||
|
import {
|
||||||
|
chevronBackOutline,
|
||||||
|
ellipsisHorizontal,
|
||||||
|
ellipsisVertical,
|
||||||
|
heart,
|
||||||
|
logoIonic,
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
import AboutPopover from '../../components/AboutPopover';
|
||||||
|
import { format, parseISO } from 'date-fns';
|
||||||
|
import { TestContent } from './TestContent';
|
||||||
|
import { Helloworld } from '../../api/Helloworld';
|
||||||
|
import { getEventById } from '../../api/getEventById';
|
||||||
|
import { connect } from '../../data/connect';
|
||||||
|
import * as selectors from '../../data/selectors';
|
||||||
|
import { Event } from '../../models/Event';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
|
||||||
|
const leftShift: number = 10;
|
||||||
|
const thumbnailSize: number = 40;
|
||||||
|
|
||||||
|
interface OwnProps extends RouteComponentProps {
|
||||||
|
event_detail?: Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {}
|
||||||
|
|
||||||
|
interface DispatchProps {}
|
||||||
|
|
||||||
|
interface EventDetailProps {
|
||||||
|
avatars: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const AvatarRow: React.FC<{ avatars: string[] }> = ({ avatars }) => {
|
||||||
|
const router = useIonRouter();
|
||||||
|
|
||||||
|
const [showPopover, setShowPopover] = useState(false);
|
||||||
|
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||||
|
const [location, setLocation] = useState<'madison' | 'austin' | 'chicago' | 'seattle'>('madison');
|
||||||
|
const [conferenceDate, setConferenceDate] = useState('2047-05-17T00:00:00-05:00');
|
||||||
|
|
||||||
|
const selectOptions = {
|
||||||
|
header: 'Select a Location',
|
||||||
|
};
|
||||||
|
|
||||||
|
const presentPopover = (e: React.MouseEvent) => {
|
||||||
|
setPopoverEvent(e.nativeEvent);
|
||||||
|
setShowPopover(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
function displayDate(date: string, dateFormat: string) {
|
||||||
|
return format(parseISO(date), dateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [eventDetail, setEventDetail] = useState<Event | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
Helloworld();
|
||||||
|
getEventById('1').then(({ data }) => {
|
||||||
|
console.log({ data });
|
||||||
|
setEventDetail(data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function handleBackOnClick() {
|
||||||
|
router.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: 'inline-flex', alignItems: 'center' }}>
|
||||||
|
{avatars.slice(0, 3).map((m_avatar, idx) => (
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
idx == 0
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
position: 'relative',
|
||||||
|
width: `calc( ${thumbnailSize}px - ${leftShift}px )`,
|
||||||
|
left: `-${leftShift}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IonThumbnail
|
||||||
|
style={{
|
||||||
|
'--size': `${thumbnailSize}px`,
|
||||||
|
'--border-radius': `${thumbnailSize / 2}px`,
|
||||||
|
border: '3px solid white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img alt="Silhouette of a person's head" src={m_avatar} />
|
||||||
|
</IonThumbnail>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div style={{ marginLeft: '0.1rem', fontWeight: 'bold' }}>
|
||||||
|
{' '}
|
||||||
|
+{avatars.length - 3} going{' '}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AvatarRow;
|
@@ -26,14 +26,31 @@ import {
|
|||||||
IonText,
|
IonText,
|
||||||
IonFooter,
|
IonFooter,
|
||||||
useIonRouter,
|
useIonRouter,
|
||||||
|
IonAvatar,
|
||||||
} from '@ionic/react';
|
} from '@ionic/react';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
import {
|
import {
|
||||||
|
accessibility,
|
||||||
|
accessibilityOutline,
|
||||||
chevronBackOutline,
|
chevronBackOutline,
|
||||||
ellipsisHorizontal,
|
ellipsisHorizontal,
|
||||||
ellipsisVertical,
|
ellipsisVertical,
|
||||||
heart,
|
heart,
|
||||||
|
locationOutline,
|
||||||
|
locationSharp,
|
||||||
logoIonic,
|
logoIonic,
|
||||||
|
man,
|
||||||
|
manOutline,
|
||||||
|
people,
|
||||||
|
peopleOutline,
|
||||||
|
timer,
|
||||||
|
timerOutline,
|
||||||
|
timerSharp,
|
||||||
|
wallet,
|
||||||
|
walletOutline,
|
||||||
|
walletSharp,
|
||||||
|
woman,
|
||||||
|
womanOutline,
|
||||||
} from 'ionicons/icons';
|
} from 'ionicons/icons';
|
||||||
import AboutPopover from '../../components/AboutPopover';
|
import AboutPopover from '../../components/AboutPopover';
|
||||||
import { format, parseISO } from 'date-fns';
|
import { format, parseISO } from 'date-fns';
|
||||||
@@ -44,28 +61,46 @@ import { connect } from '../../data/connect';
|
|||||||
import * as selectors from '../../data/selectors';
|
import * as selectors from '../../data/selectors';
|
||||||
import { Event } from '../../models/Event';
|
import { Event } from '../../models/Event';
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import AvatarRow from './AvatarRow';
|
||||||
|
|
||||||
|
const leftShift: number = -25;
|
||||||
|
|
||||||
interface OwnProps extends RouteComponentProps {
|
interface OwnProps extends RouteComponentProps {
|
||||||
event?: Event;
|
event_detail?: Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StateProps {}
|
interface StateProps {}
|
||||||
|
|
||||||
interface DispatchProps {}
|
interface DispatchProps {}
|
||||||
|
|
||||||
interface SpeakerDetailProps extends OwnProps, StateProps, DispatchProps {}
|
interface EventDetailProps extends OwnProps, StateProps, DispatchProps {}
|
||||||
|
|
||||||
interface AboutProps {}
|
const showJoinedMembers = (joinMembers: Record<string, any>[]) => {
|
||||||
|
const avatars = joinMembers.map((jm) => jm.avatar);
|
||||||
|
|
||||||
|
console.log({ joinMembers });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AvatarRow avatars={avatars} />
|
||||||
|
<IonButton style={{ '--padding-start': '20px', '--padding-end': '20px' }} shape="round">
|
||||||
|
More
|
||||||
|
</IonButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EventDetail: React.FC<EventDetailProps> = ({ event_detail }) => {
|
||||||
|
const router = useIonRouter();
|
||||||
|
|
||||||
const EventDetail: React.FC<SpeakerDetailProps> = () => {
|
|
||||||
const [showPopover, setShowPopover] = useState(false);
|
const [showPopover, setShowPopover] = useState(false);
|
||||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||||
const [location, setLocation] = useState<
|
const [location, setLocation] = useState<'madison' | 'austin' | 'chicago' | 'seattle'>('madison');
|
||||||
'madison' | 'austin' | 'chicago' | 'seattle'
|
const [conferenceDate, setConferenceDate] = useState('2047-05-17T00:00:00-05:00');
|
||||||
>('madison');
|
|
||||||
const [conferenceDate, setConferenceDate] = useState(
|
const [totalJoinMembers, setTotalJoinMembers] = useState<number>(0);
|
||||||
'2047-05-17T00:00:00-05:00'
|
const [maleMembers, setMaleMembers] = useState<number>(0);
|
||||||
);
|
const [femaleMembers, setFemaleMembers] = useState<number>(0);
|
||||||
|
|
||||||
const selectOptions = {
|
const selectOptions = {
|
||||||
header: 'Select a Location',
|
header: 'Select a Location',
|
||||||
@@ -80,6 +115,14 @@ const EventDetail: React.FC<SpeakerDetailProps> = () => {
|
|||||||
return format(parseISO(date), dateFormat);
|
return format(parseISO(date), dateFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (event_detail) {
|
||||||
|
setTotalJoinMembers(event_detail.joinMembers.length);
|
||||||
|
setMaleMembers(event_detail.joinMembers.filter((m) => m.sex == 'M').length);
|
||||||
|
setFemaleMembers(event_detail.joinMembers.filter((m) => m.sex == 'F').length);
|
||||||
|
}
|
||||||
|
}, [event_detail]);
|
||||||
|
|
||||||
const [eventDetail, setEventDetail] = useState<Event | null>(null);
|
const [eventDetail, setEventDetail] = useState<Event | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Helloworld();
|
Helloworld();
|
||||||
@@ -89,15 +132,14 @@ const EventDetail: React.FC<SpeakerDetailProps> = () => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const router = useIonRouter();
|
|
||||||
function handleBackOnClick() {
|
function handleBackOnClick() {
|
||||||
router.goBack();
|
router.goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventDetail) return <>loading</>;
|
if (!event_detail) return <>loading</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IonPage id="about-page">
|
<IonPage id="event-detail-page">
|
||||||
<IonContent>
|
<IonContent>
|
||||||
<IonHeader className="ion-no-border">
|
<IonHeader className="ion-no-border">
|
||||||
<IonToolbar>
|
<IonToolbar>
|
||||||
@@ -109,63 +151,110 @@ const EventDetail: React.FC<SpeakerDetailProps> = () => {
|
|||||||
</IonButtons>
|
</IonButtons>
|
||||||
<IonButtons slot="end">
|
<IonButtons slot="end">
|
||||||
<IonButton onClick={presentPopover}>
|
<IonButton onClick={presentPopover}>
|
||||||
<IonIcon
|
<IonIcon slot="icon-only" ios={ellipsisHorizontal} md={ellipsisVertical}></IonIcon>
|
||||||
slot="icon-only"
|
|
||||||
ios={ellipsisHorizontal}
|
|
||||||
md={ellipsisVertical}
|
|
||||||
></IonIcon>
|
|
||||||
</IonButton>
|
</IonButton>
|
||||||
</IonButtons>
|
</IonButtons>
|
||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
|
|
||||||
<div className="about-header">
|
<div className="about-header">
|
||||||
<div className="about-image madison" style={{ opacity: 1 }}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>{eventDetail.avatar}</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>{format(new Date(eventDetail.eventDate), 'yyyy-MM-dd')}</div>
|
|
||||||
<h1>{eventDetail.title}</h1>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
|
||||||
<div>members place holder</div>
|
|
||||||
<IonButton shape="round">More</IonButton>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
className="about-image madison"
|
||||||
marginBottom: '1rem',
|
style={{ opacity: 1, backgroundImage: `url(${event_detail.avatar[0]})` }}
|
||||||
paddingTop: '1rem',
|
|
||||||
borderBottom: '1px solid black',
|
|
||||||
}}
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
||||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
<div style={{ paddingLeft: '0.5rem', paddingRight: '0.5rem' }}>
|
||||||
<div>{eventDetail.currency}</div>
|
<div>
|
||||||
<div>{eventDetail.price}</div>
|
<div style={{ paddingTop: '0.25rem', color: '#007AFF' }}>
|
||||||
per person
|
{format(new Date(event_detail.eventDate), 'EEE, dd MMM yyyy, hh:mm a')}
|
||||||
|
</div>
|
||||||
|
<div style={{ paddingTop: '0.25rem', fontSize: '1.8rem', fontWeight: '500' }}>
|
||||||
|
{event_detail.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '1rem',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event_detail.joinMembers && event_detail.joinMembers.length > 0 ? (
|
||||||
|
showJoinedMembers(event_detail.joinMembers)
|
||||||
|
) : (
|
||||||
|
<>join fast !</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '1rem',
|
||||||
|
paddingTop: '1rem',
|
||||||
|
borderBottom: '1px solid gray',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingLeft: '0.5rem',
|
||||||
|
paddingRight: '0.5rem',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
|
<IonIcon icon={walletSharp} style={{ fontSize: '1.5rem' }}></IonIcon>
|
||||||
|
<div style={{ display: 'flex', gap: '0.15rem', alignItems: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>{event_detail.currency}</div>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>{event_detail.price}</div>
|
||||||
|
per person
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>{eventDetail.duration_m}</div>
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
|
<IonIcon icon={timerSharp} style={{ fontSize: '1.5rem' }}></IonIcon>
|
||||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
<div style={{ display: 'flex', gap: '0.15rem', alignItems: 'center' }}>
|
||||||
<div>{eventDetail.ageBottom}</div>
|
{event_detail.duration_m}
|
||||||
<div>{eventDetail.ageTop}</div>
|
<div>mins</div>
|
||||||
<div>years old</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>{eventDetail.location}</div>
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
<IonIcon icon={people} style={{ fontSize: '1.5rem' }}></IonIcon>
|
||||||
<IonIcon icon={logoIonic}></IonIcon>
|
<div style={{ display: 'flex', gap: '0.15rem', alignItems: 'center' }}>
|
||||||
<div>40</div>
|
<div>{event_detail.ageBottom}</div>~<div>{event_detail.ageTop}</div>
|
||||||
|
<div>years old</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<IonIcon icon={logoIonic}></IonIcon>
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
<div>20</div>
|
<IonIcon icon={locationSharp} style={{ fontSize: '1.5rem' }}></IonIcon>
|
||||||
|
{event_detail.location}
|
||||||
|
</div>
|
||||||
|
|
||||||
<IonIcon icon={logoIonic}></IonIcon>
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
<div>20</div>
|
<IonIcon
|
||||||
|
icon={accessibility}
|
||||||
|
style={{ fontSize: '1.5rem', color: 'rgb(139, 44, 245)' }}
|
||||||
|
></IonIcon>
|
||||||
|
<div>{totalJoinMembers}</div>
|
||||||
|
|
||||||
|
<IonIcon
|
||||||
|
icon={man}
|
||||||
|
style={{ fontSize: '1.5rem', color: 'rgb(67, 110, 205)' }}
|
||||||
|
></IonIcon>
|
||||||
|
<div>{maleMembers}</div>
|
||||||
|
|
||||||
|
<IonIcon
|
||||||
|
icon={woman}
|
||||||
|
style={{ fontSize: '1.5rem', color: 'rgb(235, 50, 35)' }}
|
||||||
|
></IonIcon>
|
||||||
|
<div>{femaleMembers}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
@@ -197,8 +286,11 @@ const EventDetail: React.FC<SpeakerDetailProps> = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default connect({
|
export default connect({
|
||||||
mapStateToProps: (state, ownProps) => ({
|
mapStateToProps: (state, ownProps) => {
|
||||||
event: selectors.getEvent(state, ownProps),
|
console.log({ t1: selectors.getEvents(state) });
|
||||||
}),
|
return {
|
||||||
|
event_detail: selectors.getEvent(state, ownProps),
|
||||||
|
};
|
||||||
|
},
|
||||||
component: EventDetail,
|
component: EventDetail,
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
#about-page {
|
#event-detail-page {
|
||||||
ion-toolbar {
|
ion-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { IonIcon } from '@ionic/react';
|
||||||
|
import { woman } from 'ionicons/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function NumOfFemaleMemberJoin({ joinMembers }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonIcon icon={woman} style={{ fontSize: '1.1rem', color: 'rgb(235, 50, 35)' }}></IonIcon>
|
||||||
|
<div>{joinMembers.filter((jm) => jm.sex == 'F').length}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
12
03_source/mobile/src/pages/EventList/NumOfMaleMemberJoin.tsx
Normal file
12
03_source/mobile/src/pages/EventList/NumOfMaleMemberJoin.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { IonIcon } from '@ionic/react';
|
||||||
|
import { man } from 'ionicons/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function NumOfMaleMemberJoin({ joinMembers }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonIcon icon={man} style={{ fontSize: '1.1rem', color: 'rgb(67, 110, 205)' }}></IonIcon>
|
||||||
|
<div>{joinMembers.filter((jm) => jm.sex == 'M').length}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
15
03_source/mobile/src/pages/EventList/NumOfMemberJoin.tsx
Normal file
15
03_source/mobile/src/pages/EventList/NumOfMemberJoin.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { IonIcon } from '@ionic/react';
|
||||||
|
import { accessibility } from 'ionicons/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function NumOfMemberJoin({ joinMembers }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonIcon
|
||||||
|
icon={accessibility}
|
||||||
|
style={{ fontSize: '1.1rem', color: 'rgb(139, 44, 245)' }}
|
||||||
|
></IonIcon>
|
||||||
|
<div>{joinMembers.length}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
// REQ0041/home_discover_event_tab
|
// REQ0041/home_discover_event_tab
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
IonHeader,
|
IonHeader,
|
||||||
IonToolbar,
|
IonToolbar,
|
||||||
@@ -8,54 +8,49 @@ import {
|
|||||||
IonContent,
|
IonContent,
|
||||||
IonPage,
|
IonPage,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonMenuButton,
|
|
||||||
IonGrid,
|
IonGrid,
|
||||||
IonRow,
|
IonRow,
|
||||||
IonCol,
|
IonCol,
|
||||||
useIonRouter,
|
useIonRouter,
|
||||||
IonButton,
|
IonButton,
|
||||||
IonIcon,
|
IonIcon,
|
||||||
IonPopover,
|
|
||||||
IonAvatar,
|
|
||||||
IonImg,
|
|
||||||
IonItem,
|
|
||||||
IonLabel,
|
|
||||||
IonList,
|
|
||||||
IonModal,
|
IonModal,
|
||||||
IonSearchbar,
|
|
||||||
useIonModal,
|
|
||||||
IonInput,
|
|
||||||
IonRefresher,
|
IonRefresher,
|
||||||
IonRefresherContent,
|
IonRefresherContent,
|
||||||
RefresherEventDetail,
|
RefresherEventDetail,
|
||||||
} from '@ionic/react';
|
} from '@ionic/react';
|
||||||
import SpeakerItem from '../../components/SpeakerItem';
|
|
||||||
import { Speaker } from '../../models/Speaker';
|
|
||||||
import { Session } from '../../models/Schedule';
|
|
||||||
import { connect } from '../../data/connect';
|
import { connect } from '../../data/connect';
|
||||||
import * as selectors from '../../data/selectors';
|
import * as selectors from '../../data/selectors';
|
||||||
import '../SpeakerList.scss';
|
import '../SpeakerList.scss';
|
||||||
import { getEvents } from '../../api/getEvents';
|
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { Event } from './types';
|
|
||||||
import { chevronDownCircleOutline, heart, menuOutline } from 'ionicons/icons';
|
// import { Event } from './types';
|
||||||
import AboutPopover from '../../components/AboutPopover';
|
import { chevronDownCircleOutline, menuOutline } from 'ionicons/icons';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import { Event } from '../../models/Event';
|
||||||
|
//
|
||||||
|
import { NumOfMemberJoin } from './NumOfMemberJoin';
|
||||||
|
import { NumOfMaleMemberJoin } from './NumOfMaleMemberJoin';
|
||||||
|
import { NumOfFemaleMemberJoin } from './NumOfFemaleMemberJoin';
|
||||||
|
|
||||||
interface OwnProps {}
|
interface OwnProps {}
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
events: Event[];
|
fetchEventResult: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {}
|
interface DispatchProps {}
|
||||||
|
|
||||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||||
|
|
||||||
const EventList: React.FC<SpeakerListProps> = ({ events }) => {
|
const EventList: React.FC<SpeakerListProps> = ({ fetchEventResult }) => {
|
||||||
|
const router = useIonRouter();
|
||||||
const modal = useRef<HTMLIonModalElement>(null);
|
const modal = useRef<HTMLIonModalElement>(null);
|
||||||
|
|
||||||
const router = useIonRouter();
|
const {
|
||||||
|
result: { status },
|
||||||
|
data: { events },
|
||||||
|
} = fetchEventResult;
|
||||||
|
|
||||||
function handleShowPartyEventDetail(event_id: string) {
|
function handleShowPartyEventDetail(event_id: string) {
|
||||||
router.push(`/event_detail/${event_id}`);
|
router.push(`/event_detail/${event_id}`);
|
||||||
@@ -68,6 +63,9 @@ const EventList: React.FC<SpeakerListProps> = ({ events }) => {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status != 200)
|
||||||
|
return <>Error during fetching event list, check /events endpoint if working</>;
|
||||||
|
|
||||||
if (!events || events.length == 0) return <Loading />;
|
if (!events || events.length == 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -84,9 +82,14 @@ const EventList: React.FC<SpeakerListProps> = ({ events }) => {
|
|||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
|
|
||||||
<IonContent fullscreen={true}>
|
<IonContent className="ion-padding" fullscreen={true}>
|
||||||
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
||||||
<IonRefresherContent pullingIcon={chevronDownCircleOutline} pullingText="Pull to refresh" refreshingSpinner="circles" refreshingText="Refreshing..."></IonRefresherContent>
|
<IonRefresherContent
|
||||||
|
pullingIcon={chevronDownCircleOutline}
|
||||||
|
pullingText="Pull to refresh"
|
||||||
|
refreshingSpinner="circles"
|
||||||
|
refreshingText="Refreshing..."
|
||||||
|
></IonRefresherContent>
|
||||||
</IonRefresher>
|
</IonRefresher>
|
||||||
|
|
||||||
<IonHeader collapse="condense">
|
<IonHeader collapse="condense">
|
||||||
@@ -101,35 +104,43 @@ const EventList: React.FC<SpeakerListProps> = ({ events }) => {
|
|||||||
<IonCol size="12" size-md="6" key={idx}>
|
<IonCol size="12" size-md="6" key={idx}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: '1rem',
|
border: '1px solid lightgrey',
|
||||||
border: '1px solid black',
|
|
||||||
borderRadius: '1rem',
|
borderRadius: '1rem',
|
||||||
}}
|
}}
|
||||||
onClick={() => handleShowPartyEventDetail(event.id)}
|
onClick={() => handleShowPartyEventDetail(event.id)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url("https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc")`,
|
backgroundImage: `url(${event.avatar[0]})`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
height: '33vw',
|
height: '33vw',
|
||||||
|
//
|
||||||
|
borderRadius: '1rem 1rem 0 0',
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div style={{ marginTop: '1rem' }}>
|
||||||
style={{
|
<div
|
||||||
display: 'flex',
|
style={{
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
gap: '1rem',
|
flexDirection: 'column',
|
||||||
//
|
gap: '0.5rem',
|
||||||
marginTop: '1rem',
|
padding: '0.5rem',
|
||||||
}}
|
paddingBottom: '1rem',
|
||||||
>
|
}}
|
||||||
<div>{format(new Date(event.eventDate), 'yyyy-MM-dd')}</div>
|
>
|
||||||
<div>{event.title}</div>
|
{/* <div>{format(new Date(event.eve ntDate), 'yyyy-MM-dd')}</div> */}
|
||||||
<div>{event.currency}</div>
|
<div style={{ color: 'rgb(0, 122, 255)' }}>
|
||||||
<div>{event.price}</div>
|
{format(new Date(event.eventDate), 'EEE, dd MMM yyyy, hh:mm a')}
|
||||||
<div>
|
</div>
|
||||||
{40} {20} {20}
|
<div style={{ fontSize: '1.2rem', fontWeight: 'bold' }}>{event.name}</div>
|
||||||
|
<div style={{ fontSize: '1.2rem', fontWeight: 'bold' }}>{event.price}</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '0.25rem', alignItems: 'center' }}>
|
||||||
|
<NumOfMemberJoin joinMembers={event.joinMembers} />
|
||||||
|
<NumOfMaleMemberJoin joinMembers={event.joinMembers} />
|
||||||
|
<NumOfFemaleMemberJoin joinMembers={event.joinMembers} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +151,12 @@ const EventList: React.FC<SpeakerListProps> = ({ events }) => {
|
|||||||
</IonContent>
|
</IonContent>
|
||||||
|
|
||||||
{/* REQ0079/event-filter */}
|
{/* REQ0079/event-filter */}
|
||||||
<IonModal ref={modal} trigger="events-open-modal" initialBreakpoint={0.5} breakpoints={[0, 0.25, 0.5, 0.75]}>
|
<IonModal
|
||||||
|
ref={modal}
|
||||||
|
trigger="events-open-modal"
|
||||||
|
initialBreakpoint={0.5}
|
||||||
|
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||||
|
>
|
||||||
<IonContent className="ion-padding">
|
<IonContent className="ion-padding">
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -203,8 +219,10 @@ const EventList: React.FC<SpeakerListProps> = ({ events }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||||
mapStateToProps: (state) => ({
|
mapStateToProps: (state) => {
|
||||||
events: selectors.getEvents(state),
|
return {
|
||||||
}),
|
fetchEventResult: selectors.getEvents(state),
|
||||||
|
};
|
||||||
|
},
|
||||||
component: React.memo(EventList),
|
component: React.memo(EventList),
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
export interface Event {
|
// OBSOLETED
|
||||||
|
export interface EventOBSOLETED {
|
||||||
eventDate: Date;
|
eventDate: Date;
|
||||||
joinMembers: undefined;
|
joinMembers: undefined;
|
||||||
title: string;
|
title: string;
|
||||||
|
173
03_source/mobile/src/pages/OrderDetail/index.tsx
Normal file
173
03_source/mobile/src/pages/OrderDetail/index.tsx
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// REQ0041/home_discover_event_tab
|
||||||
|
|
||||||
|
import {
|
||||||
|
IonPage,
|
||||||
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonButtons,
|
||||||
|
IonButton,
|
||||||
|
IonIcon,
|
||||||
|
IonTitle,
|
||||||
|
IonContent,
|
||||||
|
useIonRouter,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { chevronBackOutline, menuOutline } from 'ionicons/icons';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import { IOrderItem } from '../../models/Order';
|
||||||
|
import { connect } from '../../data/connect';
|
||||||
|
import * as selectors from '../../data/selectors';
|
||||||
|
|
||||||
|
import './style.scss';
|
||||||
|
import paths from '../../paths';
|
||||||
|
|
||||||
|
interface OwnProps {}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
order: IOrderItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {}
|
||||||
|
|
||||||
|
interface OrderDetailProps extends OwnProps, StateProps, DispatchProps {}
|
||||||
|
|
||||||
|
const OrderDetail: React.FC<OrderDetailProps> = ({ order }) => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const router = useIonRouter();
|
||||||
|
|
||||||
|
function handleBackClick() {
|
||||||
|
router.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="speaker-list">
|
||||||
|
<IonHeader translucent={true} className="ion-no-border">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
{/* <IonMenuButton /> */}
|
||||||
|
<IonButton
|
||||||
|
shape="round"
|
||||||
|
id="events-open-modal"
|
||||||
|
expand="block"
|
||||||
|
onClick={handleBackClick}
|
||||||
|
>
|
||||||
|
<IonIcon slot="icon-only" icon={chevronBackOutline}></IonIcon>
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>Order Details (訂單詳情)</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent className="ion-padding" fullscreen={true}>
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Total</div>
|
||||||
|
<div>{order.totalAmount}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>created at:</div>
|
||||||
|
<div>{order.createdAt}</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>updated at:</div>
|
||||||
|
<div>{order.updatedAt}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>History</h2>
|
||||||
|
<h3>Delivery</h3>
|
||||||
|
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||||
|
<div>
|
||||||
|
{order.history?.timeline.map((t) => (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>{t.title}</div>
|
||||||
|
<div>{t.time}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>擇要</h3>
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Order time</div>
|
||||||
|
<div>29 May 2025 4:01 pm</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Payment time</div>
|
||||||
|
<div>29 May 2025 4:01 pm</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Delivery time for the carrier</div>
|
||||||
|
<div>29 May 2025 4:01 pm</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Completion time</div>
|
||||||
|
<div>29 May 2025 4:01 pm</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Delivery</h2>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Ship by</div>
|
||||||
|
<div>{order.delivery.shipBy}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Speedy</div>
|
||||||
|
<div>{order.delivery.speedy}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Tracking No.</div>
|
||||||
|
<div>{order.delivery.trackingNumber}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Shipping</h2>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Address</div>
|
||||||
|
<div>{order.shippingAddress.fullAddress}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Phone Number</div>
|
||||||
|
<div>{order.shippingAddress.phoneNumber}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Payment</h2>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Card Type</div>
|
||||||
|
<div>{order.payment.cardType}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>Card Number</div>
|
||||||
|
<div>{order.payment.cardNumber}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect({
|
||||||
|
mapStateToProps: (state, ownProps) => ({
|
||||||
|
order: selectors.getOrder(state, ownProps),
|
||||||
|
}),
|
||||||
|
component: React.memo(OrderDetail),
|
||||||
|
});
|
2
03_source/mobile/src/pages/OrderDetail/style.scss
Normal file
2
03_source/mobile/src/pages/OrderDetail/style.scss
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#order-detail-page {
|
||||||
|
}
|
@@ -31,7 +31,7 @@ import {
|
|||||||
} from '@ionic/react';
|
} from '@ionic/react';
|
||||||
import SpeakerItem from '../../components/SpeakerItem';
|
import SpeakerItem from '../../components/SpeakerItem';
|
||||||
import { Speaker } from '../../models/Speaker';
|
import { Speaker } from '../../models/Speaker';
|
||||||
import { Order } from '../../models/Order';
|
import { IOrderItem } from '../../models/Order';
|
||||||
import { Session } from '../../models/Schedule';
|
import { Session } from '../../models/Schedule';
|
||||||
import { connect } from '../../data/connect';
|
import { connect } from '../../data/connect';
|
||||||
import * as selectors from '../../data/selectors';
|
import * as selectors from '../../data/selectors';
|
||||||
@@ -39,7 +39,15 @@ import '../SpeakerList.scss';
|
|||||||
import { getEvents } from '../../api/getEvents';
|
import { getEvents } from '../../api/getEvents';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
// import { Order } from './types';
|
// import { Order } from './types';
|
||||||
import { bookmarksOutline, chevronBackOutline, chevronDownCircleOutline, chevronForwardOutline, heart, logoIonic, menuOutline } from 'ionicons/icons';
|
import {
|
||||||
|
bookmarksOutline,
|
||||||
|
chevronBackOutline,
|
||||||
|
chevronDownCircleOutline,
|
||||||
|
chevronForwardOutline,
|
||||||
|
heart,
|
||||||
|
logoIonic,
|
||||||
|
menuOutline,
|
||||||
|
} from 'ionicons/icons';
|
||||||
import AboutPopover from '../../components/AboutPopover';
|
import AboutPopover from '../../components/AboutPopover';
|
||||||
import { getOrders } from '../../api/getOrders';
|
import { getOrders } from '../../api/getOrders';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
@@ -48,7 +56,7 @@ import paths from '../../paths';
|
|||||||
interface OwnProps {}
|
interface OwnProps {}
|
||||||
|
|
||||||
interface StateProps {
|
interface StateProps {
|
||||||
orders: Order[];
|
fetchOrderResult: { result: { status: number; ok: boolean }; data: IOrderItem[] };
|
||||||
//
|
//
|
||||||
speakerSessions: { [key: string]: Session[] };
|
speakerSessions: { [key: string]: Session[] };
|
||||||
}
|
}
|
||||||
@@ -78,9 +86,19 @@ const NumApplicants: React.FC<{ amount: number }> = ({ amount }) => {
|
|||||||
const TotalAmount: React.FC<{ amount: number }> = ({ amount }) => {
|
const TotalAmount: React.FC<{ amount: number }> = ({ amount }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '1.1rem' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '1.1rem' }}>
|
||||||
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold', fontSize: '1.2rem', opacity: 0.8 }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '1rem',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '1.2rem',
|
||||||
|
opacity: 0.8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>Total:</div>
|
<div>Total:</div>
|
||||||
<div style={{ minWidth: '75px', display: 'inline-flex', justifyContent: 'flex-end' }}>{amount} </div>
|
<div style={{ minWidth: '75px', display: 'inline-flex', justifyContent: 'flex-end' }}>
|
||||||
|
{amount}{' '}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -91,7 +109,9 @@ const Subtotal: React.FC<{ amount: number }> = ({ amount }) => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
|
||||||
<div>Subtotal:</div>
|
<div>Subtotal:</div>
|
||||||
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>{amount} </div>
|
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>
|
||||||
|
{amount}{' '}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -102,7 +122,9 @@ const Shipping: React.FC<{ amount: number }> = ({ amount }) => {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
|
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
|
||||||
<div>Shipping:</div>
|
<div>Shipping:</div>
|
||||||
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>{amount} </div>
|
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>
|
||||||
|
{amount}{' '}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -114,7 +136,9 @@ const Discount: React.FC<{ amount: number }> = ({ amount }) => {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
|
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
|
||||||
<div>Discount:</div>
|
<div>Discount:</div>
|
||||||
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>{amount} </div>
|
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>
|
||||||
|
{amount}{' '}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -126,25 +150,25 @@ const Tax: React.FC<{ amount: number }> = ({ amount }) => {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
|
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
|
||||||
<div>Tax:</div>
|
<div>Tax:</div>
|
||||||
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>{amount} </div>
|
<div style={{ minWidth: '50px', display: 'inline-flex', justifyContent: 'flex-end' }}>
|
||||||
|
{amount}{' '}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
const OrderList: React.FC<SpeakerListProps> = ({ fetchOrderResult, speakerSessions }) => {
|
||||||
const router = useIonRouter();
|
const router = useIonRouter();
|
||||||
|
|
||||||
const [showPopover, setShowPopover] = useState(false);
|
const [showPopover, setShowPopover] = useState(false);
|
||||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||||
const modal = useRef<HTMLIonModalElement>(null);
|
const modal = useRef<HTMLIonModalElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
// getOrders().then(({ data }) => {
|
result: { status },
|
||||||
// console.log({ data });
|
data: { orders },
|
||||||
// setEvents(data);
|
} = fetchOrderResult;
|
||||||
// });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function handleRefresh(event: CustomEvent<RefresherEventDetail>) {
|
function handleRefresh(event: CustomEvent<RefresherEventDetail>) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -165,8 +189,13 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
|||||||
router.push(paths.FAVOURITES_LIST);
|
router.push(paths.FAVOURITES_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status != 200)
|
||||||
|
return <>Error during fetching order list, check /orders endpoint if working</>;
|
||||||
|
|
||||||
if (!orders) return <Loading />;
|
if (!orders) return <Loading />;
|
||||||
|
|
||||||
|
if (orders.length == 0) return <>order list is empty</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IonPage id="speaker-list">
|
<IonPage id="speaker-list">
|
||||||
<IonHeader translucent={true} className="ion-no-border">
|
<IonHeader translucent={true} className="ion-no-border">
|
||||||
@@ -183,7 +212,12 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
|||||||
|
|
||||||
<IonContent fullscreen={true}>
|
<IonContent fullscreen={true}>
|
||||||
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
||||||
<IonRefresherContent pullingIcon={chevronDownCircleOutline} pullingText="Pull to refresh" refreshingSpinner="circles" refreshingText="Refreshing..."></IonRefresherContent>
|
<IonRefresherContent
|
||||||
|
pullingIcon={chevronDownCircleOutline}
|
||||||
|
pullingText="Pull to refresh"
|
||||||
|
refreshingSpinner="circles"
|
||||||
|
refreshingText="Refreshing..."
|
||||||
|
></IonRefresherContent>
|
||||||
</IonRefresher>
|
</IonRefresher>
|
||||||
|
|
||||||
<IonHeader collapse="condense">
|
<IonHeader collapse="condense">
|
||||||
@@ -193,17 +227,27 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
|||||||
</IonHeader>
|
</IonHeader>
|
||||||
|
|
||||||
<IonList>
|
<IonList>
|
||||||
{orders.map((order, idx) => (
|
{orders.map((order: IOrderItem, idx: number) => (
|
||||||
<IonItem button onClick={handleNotImplemented}>
|
<IonItem button onClick={() => handleShowOrderDetail(order.id)} key={idx}>
|
||||||
<div style={{ paddingBottom: '1rem', paddingTop: '1rem' }}>
|
<div style={{ paddingBottom: '1rem', paddingTop: '1rem' }}>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', width: 'calc( 100vw - 35px )' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', width: 'calc( 100vw - 35px )' }}>
|
||||||
<div style={{}}>
|
<div style={{}}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ width: '70px' }}>
|
<div style={{ width: '70px' }}>
|
||||||
<IonAvatar>
|
<IonAvatar>
|
||||||
<img alt="Silhouette of a person's head" src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc" />
|
<img
|
||||||
|
alt="Silhouette of a person's head"
|
||||||
|
src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc"
|
||||||
|
/>
|
||||||
</IonAvatar>
|
</IonAvatar>
|
||||||
<div style={{ marginTop: '1rem', display: 'inline-flex', flexDirection: 'column', gap: '0.5rem' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: '1rem',
|
||||||
|
display: 'inline-flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '0.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<NumApplicants amount={38} />
|
<NumApplicants amount={38} />
|
||||||
<RemainingDays amount={50} />
|
<RemainingDays amount={50} />
|
||||||
</div>
|
</div>
|
||||||
@@ -228,8 +272,12 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: '1.2rem' }}>{order.orderNumber}</div>
|
<div style={{ fontSize: '1.2rem' }}>{order.orderNumber}</div>
|
||||||
<IonButton shape="round" onClick={() => handleShowOrderDetail('1')} size="small" fill="clear">
|
<IonButton shape="round" size="small" fill="clear">
|
||||||
<IonIcon slot="icon-only" icon={chevronForwardOutline} size="small"></IonIcon>
|
<IonIcon
|
||||||
|
slot="icon-only"
|
||||||
|
icon={chevronForwardOutline}
|
||||||
|
size="small"
|
||||||
|
></IonIcon>
|
||||||
</IonButton>
|
</IonButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -272,9 +320,9 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
|||||||
|
|
||||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||||
mapStateToProps: (state) => ({
|
mapStateToProps: (state) => ({
|
||||||
orders: selectors.getOrders(state),
|
fetchOrderResult: selectors.getOrders(state),
|
||||||
// TODO: review below
|
// TODO: review unused code
|
||||||
speakerSessions: selectors.getSpeakerSessions(state),
|
speakerSessions: selectors.getSpeakerSessions(state),
|
||||||
}),
|
}),
|
||||||
component: React.memo(EventList),
|
component: React.memo(OrderList),
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user