"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:
louiscklaw
2025-06-03 17:04:11 +08:00
parent 24920fb313
commit 7610d80005
18 changed files with 867 additions and 246 deletions

View File

@@ -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;

View File

@@ -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[] } = {};

View File

@@ -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[];
} }

View File

@@ -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 };

View File

@@ -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>) => {

View File

@@ -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;
} }

View File

@@ -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;
} }

View 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;

View File

@@ -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,
}); });

View File

@@ -1,4 +1,4 @@
#about-page { #event-detail-page {
ion-toolbar { ion-toolbar {
position: absolute; position: absolute;

View File

@@ -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>
</>
);
}

View 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>
</>
);
}

View 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>
</>
);
}

View File

@@ -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),
}); });

View File

@@ -1,4 +1,5 @@
export interface Event { // OBSOLETED
export interface EventOBSOLETED {
eventDate: Date; eventDate: Date;
joinMembers: undefined; joinMembers: undefined;
title: string; title: string;

View 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),
});

View File

@@ -0,0 +1,2 @@
#order-detail-page {
}

View File

@@ -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),
}); });