diff --git a/03_source/mobile/src/data/dataApi.ts b/03_source/mobile/src/data/dataApi.ts index 9f39cb8..fdb26bd 100644 --- a/03_source/mobile/src/data/dataApi.ts +++ b/03_source/mobile/src/data/dataApi.ts @@ -4,6 +4,8 @@ import { Speaker } from '../models/Speaker'; import { Location } from '../models/Location'; import axios from 'axios'; import constants from '../constants'; +import { IOrderItem } from '../models/Order'; +import { Event } from '../models/Event'; const dataUrl = '/assets/data/data.json'; const locationsUrl = '/assets/data/locations.json'; @@ -16,11 +18,13 @@ export const getConfData = async () => { const response = await Promise.all([ fetch(dataUrl), 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/members`), // ]); + const responseData = await response[0].json(); const schedule = responseData.schedule[0] as Schedule; const sessions = parseSessions(schedule); @@ -33,9 +37,29 @@ export const getConfData = async () => { // const events = response[2].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 data = { @@ -47,15 +71,21 @@ export const getConfData = async () => { filteredTracks: [...allTracks], // events, - nearByMembers, + // nearByMembers, orders, + hello: 'world', // }; + return data; }; 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 hasSeenTutorial = (await response[1].value) === 'true'; const username = (await response[2].value) || undefined; diff --git a/03_source/mobile/src/data/selectors.ts b/03_source/mobile/src/data/selectors.ts index 997ac5b..1b06acc 100644 --- a/03_source/mobile/src/data/selectors.ts +++ b/03_source/mobile/src/data/selectors.ts @@ -2,9 +2,10 @@ import { createSelector } from 'reselect'; import { Schedule, Session, ScheduleGroup } from '../models/Schedule'; import { Speaker } from '../models/Speaker'; import { Location } from '../models/Location'; -import { Event } from '../models/Event'; import { AppState } from './state'; +import { IOrderItem } from '../models/Order'; +import { Event } from '../models/Event'; const getSchedule = (state: AppState) => { return state.data.schedule; @@ -17,106 +18,126 @@ const getFilteredTracks = (state: AppState) => state.data.filteredTracks; const getFavoriteIds = (state: AppState) => state.data.favorites; 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 getOrders = (state: AppState) => state.data.orders; -export const getFilteredSchedule = createSelector(getSchedule, getFilteredTracks, (schedule, filteredTracks) => { - const groups: ScheduleGroup[] = []; +export const getOrders = (state: AppState) => { + return state.data.orders; +}; - // Helper function to convert 12-hour time to 24-hour time for proper sorting - const convertTo24Hour = (timeStr: string) => { - const [time, period] = timeStr.toLowerCase().split(' '); - let [hours, minutes] = time.split(':').map(Number); +export const getFilteredSchedule = createSelector( + getSchedule, + getFilteredTracks, + (schedule, filteredTracks) => { + const groups: ScheduleGroup[] = []; - if (period === 'pm' && hours !== 12) { - hours += 12; - } else if (period === 'am' && hours === 12) { - hours = 0; - } + // Helper function to convert 12-hour time to 24-hour time for proper sorting + const convertTo24Hour = (timeStr: string) => { + const [time, period] = timeStr.toLowerCase().split(' '); + 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 - const sortedGroups = [...schedule.groups].sort((a, b) => { - const timeA = convertTo24Hour(a.time); - const timeB = convertTo24Hour(b.time); - return timeA.localeCompare(timeB); - }); + return `${hours.toString().padStart(2, '0')}:${minutes || '00'}`; + }; - sortedGroups.forEach((group: ScheduleGroup) => { - const sessions: Session[] = []; - group.sessions.forEach((session) => { - session.tracks.forEach((track) => { - if (filteredTracks.indexOf(track) > -1) { - sessions.push(session); - } - }); + // Sort the groups by time + const sortedGroups = [...schedule.groups].sort((a, b) => { + const timeA = convertTo24Hour(a.time); + const timeB = convertTo24Hour(b.time); + return timeA.localeCompare(timeB); }); - if (sessions.length) { - // Sort sessions within each group by start time - const sortedSessions = sessions.sort((a, b) => { - const timeA = convertTo24Hour(a.timeStart); - const timeB = convertTo24Hour(b.timeStart); - return timeA.localeCompare(timeB); + sortedGroups.forEach((group: ScheduleGroup) => { + const sessions: Session[] = []; + group.sessions.forEach((session) => { + session.tracks.forEach((track) => { + if (filteredTracks.indexOf(track) > -1) { + sessions.push(session); + } + }); }); - const groupToAdd: ScheduleGroup = { - time: group.time, - sessions: sortedSessions, - }; - groups.push(groupToAdd); - } - }); + if (sessions.length) { + // Sort sessions within each group by start time + const sortedSessions = sessions.sort((a, b) => { + const timeA = convertTo24Hour(a.timeStart); + const timeB = convertTo24Hour(b.timeStart); + return timeA.localeCompare(timeB); + }); - return { - date: schedule.date, - groups, - } as Schedule; -}); + const groupToAdd: ScheduleGroup = { + time: group.time, + sessions: sortedSessions, + }; + groups.push(groupToAdd); + } + }); -export const getSearchedSchedule = createSelector(getFilteredSchedule, getSearchText, (schedule, searchText) => { - if (!searchText) { - return schedule; + return { + date: schedule.date, + groups, + } as Schedule; } - const groups: ScheduleGroup[] = []; - schedule.groups.forEach((group) => { - const sessions = group.sessions.filter((s) => s.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1); - if (sessions.length) { - const groupToAdd: ScheduleGroup = { - time: group.time, - sessions, - }; - groups.push(groupToAdd); +); + +export const getSearchedSchedule = createSelector( + getFilteredSchedule, + getSearchText, + (schedule, searchText) => { + if (!searchText) { + return schedule; } - }); - return { - date: schedule.date, - groups, - } as Schedule; -}); + const groups: ScheduleGroup[] = []; + schedule.groups.forEach((group) => { + const sessions = group.sessions.filter( + (s) => s.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 + ); + 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 getGroupedFavorites = createSelector(getScheduleList, getFavoriteIds, (schedule, favoriteIds) => { - const groups: ScheduleGroup[] = []; - schedule.groups.forEach((group) => { - const sessions = group.sessions.filter((s) => favoriteIds.indexOf(s.id) > -1); - if (sessions.length) { - const groupToAdd: ScheduleGroup = { - time: group.time, - sessions, - }; - groups.push(groupToAdd); - } - }); - return { - date: schedule.date, - groups, - } as Schedule; -}); +export const getGroupedFavorites = createSelector( + getScheduleList, + getFavoriteIds, + (schedule, favoriteIds) => { + const groups: ScheduleGroup[] = []; + schedule.groups.forEach((group) => { + const sessions = group.sessions.filter((s) => favoriteIds.indexOf(s.id) > -1); + if (sessions.length) { + const groupToAdd: ScheduleGroup = { + time: group.time, + sessions, + }; + groups.push(groupToAdd); + } + }); + return { + date: schedule.date, + groups, + } as Schedule; + } +); const getIdParam = (_state: AppState, props: any) => { 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); }); -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) => { const speakerSessions: { [key: string]: Session[] } = {}; diff --git a/03_source/mobile/src/data/sessions/conf.state.ts b/03_source/mobile/src/data/sessions/conf.state.ts index 278d660..94d5c80 100644 --- a/03_source/mobile/src/data/sessions/conf.state.ts +++ b/03_source/mobile/src/data/sessions/conf.state.ts @@ -3,6 +3,7 @@ import { Speaker } from '../../models/Speaker'; import { Schedule, Session } from '../../models/Schedule'; // import { Event } from '../../models/Event'; +import { IOrderItem } from '../../models/Order'; export interface ConfState { schedule: Schedule; @@ -18,4 +19,5 @@ export interface ConfState { menuEnabled: boolean; // events: Event[]; + orders: IOrderItem[]; } diff --git a/03_source/mobile/src/data/sessions/sessions.reducer.ts b/03_source/mobile/src/data/sessions/sessions.reducer.ts index 7fa7c1a..3540821 100644 --- a/03_source/mobile/src/data/sessions/sessions.reducer.ts +++ b/03_source/mobile/src/data/sessions/sessions.reducer.ts @@ -1,10 +1,7 @@ import { SessionsActions } from './sessions.actions'; import { ConfState } from './conf.state'; -export const sessionsReducer = ( - state: ConfState, - action: SessionsActions -): ConfState => { +export const sessionsReducer = (state: ConfState, action: SessionsActions): ConfState => { switch (action.type) { case 'set-conf-loading': { return { ...state, loading: action.isLoading }; diff --git a/03_source/mobile/src/data/user/user.actions.ts b/03_source/mobile/src/data/user/user.actions.ts index adeb56d..a132470 100644 --- a/03_source/mobile/src/data/user/user.actions.ts +++ b/03_source/mobile/src/data/user/user.actions.ts @@ -31,23 +31,21 @@ export const logoutUser = () => async (dispatch: React.Dispatch) => { dispatch(setUsername()); }; -export const setIsLoggedIn = - (loggedIn: boolean) => async (dispatch: React.Dispatch) => { - await setIsLoggedInData(loggedIn); - return { - type: 'set-is-loggedin', - loggedIn, - } as const; - }; +export const setIsLoggedIn = (loggedIn: boolean) => async (dispatch: React.Dispatch) => { + await setIsLoggedInData(loggedIn); + return { + type: 'set-is-loggedin', + loggedIn, + } as const; +}; -export const setUsername = - (username?: string) => async (dispatch: React.Dispatch) => { - await setUsernameData(username); - return { - type: 'set-username', - username, - } as const; - }; +export const setUsername = (username?: string) => async (dispatch: React.Dispatch) => { + await setUsernameData(username); + return { + type: 'set-username', + username, + } as const; +}; export const setHasSeenTutorial = (hasSeenTutorial: boolean) => async (dispatch: React.Dispatch) => { diff --git a/03_source/mobile/src/models/Event.ts b/03_source/mobile/src/models/Event.ts index 2f4577f..31ac548 100644 --- a/03_source/mobile/src/models/Event.ts +++ b/03_source/mobile/src/models/Event.ts @@ -1,8 +1,19 @@ +// 03_source/mobile/src/models/Event.ts + +export type IDateValue = string | number | null; + export interface Event { - eventDate: Date; - joinMembers: undefined; - title: string; + id: string; + createdAt: IDateValue; + updatedAt: IDateValue; + // + name: string; + code: string; price: number; + // + eventDate: Date; + joinMembers: { email: string; avatar: string; sex: string }[]; + title: string; currency: string; duration_m: number; ageBottom: number; @@ -10,5 +21,4 @@ export interface Event { location: string; avatar: string; // - id: string; } diff --git a/03_source/mobile/src/models/Order.ts b/03_source/mobile/src/models/Order.ts index e483865..6d4b184 100644 --- a/03_source/mobile/src/models/Order.ts +++ b/03_source/mobile/src/models/Order.ts @@ -1,8 +1,42 @@ 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; createdAt: IDateValue; + updatedAt: IDateValue; // taxes: number; status: string; @@ -12,4 +46,10 @@ export interface Order { orderNumber: string; totalAmount: number; totalQuantity: number; + // + items: IOrderProductItem[]; + history: IOrderHistory | undefined; + delivery: IOrderDelivery; + shippingAddress: IOrderShippingAddress; + payment: IOrderPayment; } diff --git a/03_source/mobile/src/pages/EventDetail/AvatarRow.tsx b/03_source/mobile/src/pages/EventDetail/AvatarRow.tsx new file mode 100644 index 0000000..aa4292c --- /dev/null +++ b/03_source/mobile/src/pages/EventDetail/AvatarRow.tsx @@ -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(); + 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(null); + useEffect(() => { + Helloworld(); + getEventById('1').then(({ data }) => { + console.log({ data }); + setEventDetail(data); + }); + }, []); + + function handleBackOnClick() { + router.goBack(); + } + + return ( + <> +
+ {avatars.slice(0, 3).map((m_avatar, idx) => ( +
+ + Silhouette of a person's head + +
+ ))} +
+ {' '} + +{avatars.length - 3} going{' '} +
+
+ + ); +}; + +export default AvatarRow; diff --git a/03_source/mobile/src/pages/EventDetail/index.tsx b/03_source/mobile/src/pages/EventDetail/index.tsx index 2da9eeb..efc740a 100644 --- a/03_source/mobile/src/pages/EventDetail/index.tsx +++ b/03_source/mobile/src/pages/EventDetail/index.tsx @@ -26,14 +26,31 @@ import { IonText, IonFooter, useIonRouter, + IonAvatar, } from '@ionic/react'; import './style.scss'; import { + accessibility, + accessibilityOutline, chevronBackOutline, ellipsisHorizontal, ellipsisVertical, heart, + locationOutline, + locationSharp, logoIonic, + man, + manOutline, + people, + peopleOutline, + timer, + timerOutline, + timerSharp, + wallet, + walletOutline, + walletSharp, + woman, + womanOutline, } from 'ionicons/icons'; import AboutPopover from '../../components/AboutPopover'; import { format, parseISO } from 'date-fns'; @@ -44,28 +61,46 @@ import { connect } from '../../data/connect'; import * as selectors from '../../data/selectors'; import { Event } from '../../models/Event'; import { RouteComponentProps } from 'react-router'; +import AvatarRow from './AvatarRow'; + +const leftShift: number = -25; interface OwnProps extends RouteComponentProps { - event?: Event; + event_detail?: Event; } interface StateProps {} interface DispatchProps {} -interface SpeakerDetailProps extends OwnProps, StateProps, DispatchProps {} +interface EventDetailProps extends OwnProps, StateProps, DispatchProps {} -interface AboutProps {} +const showJoinedMembers = (joinMembers: Record[]) => { + const avatars = joinMembers.map((jm) => jm.avatar); + + console.log({ joinMembers }); + + return ( + <> + + + More + + + ); +}; + +const EventDetail: React.FC = ({ event_detail }) => { + const router = useIonRouter(); -const EventDetail: React.FC = () => { const [showPopover, setShowPopover] = useState(false); const [popoverEvent, setPopoverEvent] = useState(); - const [location, setLocation] = useState< - 'madison' | 'austin' | 'chicago' | 'seattle' - >('madison'); - const [conferenceDate, setConferenceDate] = useState( - '2047-05-17T00:00:00-05:00' - ); + const [location, setLocation] = useState<'madison' | 'austin' | 'chicago' | 'seattle'>('madison'); + const [conferenceDate, setConferenceDate] = useState('2047-05-17T00:00:00-05:00'); + + const [totalJoinMembers, setTotalJoinMembers] = useState(0); + const [maleMembers, setMaleMembers] = useState(0); + const [femaleMembers, setFemaleMembers] = useState(0); const selectOptions = { header: 'Select a Location', @@ -80,6 +115,14 @@ const EventDetail: React.FC = () => { 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(null); useEffect(() => { Helloworld(); @@ -89,15 +132,14 @@ const EventDetail: React.FC = () => { }); }, []); - const router = useIonRouter(); function handleBackOnClick() { router.goBack(); } - if (!eventDetail) return <>loading; + if (!event_detail) return <>loading; return ( - + @@ -109,63 +151,110 @@ const EventDetail: React.FC = () => { - +
-
-
- -
{eventDetail.avatar}
- -
-
{format(new Date(eventDetail.eventDate), 'yyyy-MM-dd')}
-

{eventDetail.title}

- -
-
members place holder
- More -
-
-
-
{eventDetail.currency}
-
{eventDetail.price}
- per person + +
+
+
+ {format(new Date(event_detail.eventDate), 'EEE, dd MMM yyyy, hh:mm a')} +
+
+ {event_detail.title} +
+ +
+ {event_detail.joinMembers && event_detail.joinMembers.length > 0 ? ( + showJoinedMembers(event_detail.joinMembers) + ) : ( + <>join fast ! + )} +
+
+
+ +
+ +
+
+ +
+
{event_detail.currency}
+
{event_detail.price}
+ per person +
-
{eventDetail.duration_m}
- -
-
{eventDetail.ageBottom}
-
{eventDetail.ageTop}
-
years old
+
+ +
+ {event_detail.duration_m} +
mins
+
-
{eventDetail.location}
-
- -
40
+
+ +
+
{event_detail.ageBottom}
~
{event_detail.ageTop}
+
years old
+
+
- -
20
+
+ + {event_detail.location} +
- -
20
+
+ +
{totalJoinMembers}
+ + +
{maleMembers}
+ + +
{femaleMembers}
@@ -197,8 +286,11 @@ const EventDetail: React.FC = () => { }; export default connect({ - mapStateToProps: (state, ownProps) => ({ - event: selectors.getEvent(state, ownProps), - }), + mapStateToProps: (state, ownProps) => { + console.log({ t1: selectors.getEvents(state) }); + return { + event_detail: selectors.getEvent(state, ownProps), + }; + }, component: EventDetail, }); diff --git a/03_source/mobile/src/pages/EventDetail/style.scss b/03_source/mobile/src/pages/EventDetail/style.scss index 5fae6e3..7635035 100644 --- a/03_source/mobile/src/pages/EventDetail/style.scss +++ b/03_source/mobile/src/pages/EventDetail/style.scss @@ -1,4 +1,4 @@ -#about-page { +#event-detail-page { ion-toolbar { position: absolute; diff --git a/03_source/mobile/src/pages/EventList/NumOfFemaleMemberJoin.tsx b/03_source/mobile/src/pages/EventList/NumOfFemaleMemberJoin.tsx new file mode 100644 index 0000000..ff0584e --- /dev/null +++ b/03_source/mobile/src/pages/EventList/NumOfFemaleMemberJoin.tsx @@ -0,0 +1,12 @@ +import { IonIcon } from '@ionic/react'; +import { woman } from 'ionicons/icons'; +import React from 'react'; + +export function NumOfFemaleMemberJoin({ joinMembers }) { + return ( + <> + +
{joinMembers.filter((jm) => jm.sex == 'F').length}
+ + ); +} diff --git a/03_source/mobile/src/pages/EventList/NumOfMaleMemberJoin.tsx b/03_source/mobile/src/pages/EventList/NumOfMaleMemberJoin.tsx new file mode 100644 index 0000000..9d1e9c9 --- /dev/null +++ b/03_source/mobile/src/pages/EventList/NumOfMaleMemberJoin.tsx @@ -0,0 +1,12 @@ +import { IonIcon } from '@ionic/react'; +import { man } from 'ionicons/icons'; +import React from 'react'; + +export function NumOfMaleMemberJoin({ joinMembers }) { + return ( + <> + +
{joinMembers.filter((jm) => jm.sex == 'M').length}
+ + ); +} diff --git a/03_source/mobile/src/pages/EventList/NumOfMemberJoin.tsx b/03_source/mobile/src/pages/EventList/NumOfMemberJoin.tsx new file mode 100644 index 0000000..1e870e9 --- /dev/null +++ b/03_source/mobile/src/pages/EventList/NumOfMemberJoin.tsx @@ -0,0 +1,15 @@ +import { IonIcon } from '@ionic/react'; +import { accessibility } from 'ionicons/icons'; +import React from 'react'; + +export function NumOfMemberJoin({ joinMembers }) { + return ( + <> + +
{joinMembers.length}
+ + ); +} diff --git a/03_source/mobile/src/pages/EventList/index.tsx b/03_source/mobile/src/pages/EventList/index.tsx index ab808db..7951440 100644 --- a/03_source/mobile/src/pages/EventList/index.tsx +++ b/03_source/mobile/src/pages/EventList/index.tsx @@ -1,6 +1,6 @@ // REQ0041/home_discover_event_tab -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef } from 'react'; import { IonHeader, IonToolbar, @@ -8,54 +8,49 @@ import { IonContent, IonPage, IonButtons, - IonMenuButton, IonGrid, IonRow, IonCol, useIonRouter, IonButton, IonIcon, - IonPopover, - IonAvatar, - IonImg, - IonItem, - IonLabel, - IonList, IonModal, - IonSearchbar, - useIonModal, - IonInput, IonRefresher, IonRefresherContent, RefresherEventDetail, } from '@ionic/react'; -import SpeakerItem from '../../components/SpeakerItem'; -import { Speaker } from '../../models/Speaker'; -import { Session } from '../../models/Schedule'; import { connect } from '../../data/connect'; import * as selectors from '../../data/selectors'; import '../SpeakerList.scss'; -import { getEvents } from '../../api/getEvents'; import { format } from 'date-fns'; -import { Event } from './types'; -import { chevronDownCircleOutline, heart, menuOutline } from 'ionicons/icons'; -import AboutPopover from '../../components/AboutPopover'; + +// import { Event } from './types'; +import { chevronDownCircleOutline, menuOutline } from 'ionicons/icons'; 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 StateProps { - events: Event[]; + fetchEventResult: any; } interface DispatchProps {} interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {} -const EventList: React.FC = ({ events }) => { +const EventList: React.FC = ({ fetchEventResult }) => { + const router = useIonRouter(); const modal = useRef(null); - const router = useIonRouter(); + const { + result: { status }, + data: { events }, + } = fetchEventResult; function handleShowPartyEventDetail(event_id: string) { router.push(`/event_detail/${event_id}`); @@ -68,6 +63,9 @@ const EventList: React.FC = ({ events }) => { }, 2000); } + if (status != 200) + return <>Error during fetching event list, check /events endpoint if working; + if (!events || events.length == 0) return ; return ( @@ -84,9 +82,14 @@ const EventList: React.FC = ({ events }) => { - + - + @@ -101,35 +104,43 @@ const EventList: React.FC = ({ events }) => {
handleShowPartyEventDetail(event.id)} >
-
-
{format(new Date(event.eventDate), 'yyyy-MM-dd')}
-
{event.title}
-
{event.currency}
-
{event.price}
-
- {40} {20} {20} +
+
+ {/*
{format(new Date(event.eve ntDate), 'yyyy-MM-dd')}
*/} +
+ {format(new Date(event.eventDate), 'EEE, dd MMM yyyy, hh:mm a')} +
+
{event.name}
+
{event.price}
+ +
+ + + +
@@ -140,7 +151,12 @@ const EventList: React.FC = ({ events }) => { {/* REQ0079/event-filter */} - +
= ({ events }) => { }; export default connect({ - mapStateToProps: (state) => ({ - events: selectors.getEvents(state), - }), + mapStateToProps: (state) => { + return { + fetchEventResult: selectors.getEvents(state), + }; + }, component: React.memo(EventList), }); diff --git a/03_source/mobile/src/pages/EventList/types.ts b/03_source/mobile/src/pages/EventList/types.ts index 2f4577f..8579fd1 100644 --- a/03_source/mobile/src/pages/EventList/types.ts +++ b/03_source/mobile/src/pages/EventList/types.ts @@ -1,4 +1,5 @@ -export interface Event { +// OBSOLETED +export interface EventOBSOLETED { eventDate: Date; joinMembers: undefined; title: string; diff --git a/03_source/mobile/src/pages/OrderDetail/index.tsx b/03_source/mobile/src/pages/OrderDetail/index.tsx new file mode 100644 index 0000000..b24533f --- /dev/null +++ b/03_source/mobile/src/pages/OrderDetail/index.tsx @@ -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 = ({ order }) => { + const { id } = useParams<{ id: string }>(); + const router = useIonRouter(); + + function handleBackClick() { + router.goBack(); + } + + return ( + + + + + {/* */} + + + + + Order Details (訂單詳情) + + + + +
+
+
Total
+
{order.totalAmount}
+
+
+
+
created at:
+
{order.createdAt}
+
+
+
updated at:
+
{order.updatedAt}
+
+
+
+

History

+

Delivery

+
+
+ {order.history?.timeline.map((t) => ( +
+
{t.title}
+
{t.time}
+
+ ))} +
+
+
+

擇要

+
+
+
Order time
+
29 May 2025 4:01 pm
+
+ +
+
Payment time
+
29 May 2025 4:01 pm
+
+ +
+
Delivery time for the carrier
+
29 May 2025 4:01 pm
+
+ +
+
Completion time
+
29 May 2025 4:01 pm
+
+
+
+
+ +
+

Delivery

+ +
+
Ship by
+
{order.delivery.shipBy}
+
+ +
+
Speedy
+
{order.delivery.speedy}
+
+ +
+
Tracking No.
+
{order.delivery.trackingNumber}
+
+
+ +
+

Shipping

+ +
+
Address
+
{order.shippingAddress.fullAddress}
+
+ +
+
Phone Number
+
{order.shippingAddress.phoneNumber}
+
+
+ +
+

Payment

+ +
+
Card Type
+
{order.payment.cardType}
+
+ +
+
Card Number
+
{order.payment.cardNumber}
+
+
+
+
+
+ ); +}; + +export default connect({ + mapStateToProps: (state, ownProps) => ({ + order: selectors.getOrder(state, ownProps), + }), + component: React.memo(OrderDetail), +}); diff --git a/03_source/mobile/src/pages/OrderDetail/style.scss b/03_source/mobile/src/pages/OrderDetail/style.scss new file mode 100644 index 0000000..42fe32e --- /dev/null +++ b/03_source/mobile/src/pages/OrderDetail/style.scss @@ -0,0 +1,2 @@ +#order-detail-page { +} diff --git a/03_source/mobile/src/pages/OrderList/index.tsx b/03_source/mobile/src/pages/OrderList/index.tsx index af2a724..b9686e5 100644 --- a/03_source/mobile/src/pages/OrderList/index.tsx +++ b/03_source/mobile/src/pages/OrderList/index.tsx @@ -31,7 +31,7 @@ import { } from '@ionic/react'; import SpeakerItem from '../../components/SpeakerItem'; import { Speaker } from '../../models/Speaker'; -import { Order } from '../../models/Order'; +import { IOrderItem } from '../../models/Order'; import { Session } from '../../models/Schedule'; import { connect } from '../../data/connect'; import * as selectors from '../../data/selectors'; @@ -39,7 +39,15 @@ import '../SpeakerList.scss'; import { getEvents } from '../../api/getEvents'; import { format } from 'date-fns'; // 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 { getOrders } from '../../api/getOrders'; import Loading from '../../components/Loading'; @@ -48,7 +56,7 @@ import paths from '../../paths'; interface OwnProps {} interface StateProps { - orders: Order[]; + fetchOrderResult: { result: { status: number; ok: boolean }; data: IOrderItem[] }; // speakerSessions: { [key: string]: Session[] }; } @@ -78,9 +86,19 @@ const NumApplicants: React.FC<{ amount: number }> = ({ amount }) => { const TotalAmount: React.FC<{ amount: number }> = ({ amount }) => { return (
-
+
Total:
-
{amount}
+
+ {amount}{' '} +
); @@ -91,7 +109,9 @@ const Subtotal: React.FC<{ amount: number }> = ({ amount }) => { return (
Subtotal:
-
{amount}
+
+ {amount}{' '} +
); }; @@ -102,7 +122,9 @@ const Shipping: React.FC<{ amount: number }> = ({ amount }) => {
Shipping:
-
{amount}
+
+ {amount}{' '} +
); @@ -114,7 +136,9 @@ const Discount: React.FC<{ amount: number }> = ({ amount }) => {
Discount:
-
{amount}
+
+ {amount}{' '} +
); @@ -126,25 +150,25 @@ const Tax: React.FC<{ amount: number }> = ({ amount }) => {
Tax:
-
{amount}
+
+ {amount}{' '} +
); }; -const EventList: React.FC = ({ orders, speakerSessions }) => { +const OrderList: React.FC = ({ fetchOrderResult, speakerSessions }) => { const router = useIonRouter(); const [showPopover, setShowPopover] = useState(false); const [popoverEvent, setPopoverEvent] = useState(); const modal = useRef(null); - useEffect(() => { - // getOrders().then(({ data }) => { - // console.log({ data }); - // setEvents(data); - // }); - }, []); + const { + result: { status }, + data: { orders }, + } = fetchOrderResult; function handleRefresh(event: CustomEvent) { setTimeout(() => { @@ -165,8 +189,13 @@ const EventList: React.FC = ({ orders, speakerSessions }) => { router.push(paths.FAVOURITES_LIST); } + if (status != 200) + return <>Error during fetching order list, check /orders endpoint if working; + if (!orders) return ; + if (orders.length == 0) return <>order list is empty; + return ( @@ -183,7 +212,12 @@ const EventList: React.FC = ({ orders, speakerSessions }) => { - + @@ -193,17 +227,27 @@ const EventList: React.FC = ({ orders, speakerSessions }) => { - {orders.map((order, idx) => ( - + {orders.map((order: IOrderItem, idx: number) => ( + handleShowOrderDetail(order.id)} key={idx}>
- Silhouette of a person's head + Silhouette of a person's head -
+
@@ -228,8 +272,12 @@ const EventList: React.FC = ({ orders, speakerSessions }) => { }} >
{order.orderNumber}
- handleShowOrderDetail('1')} size="small" fill="clear"> - + +
@@ -272,9 +320,9 @@ const EventList: React.FC = ({ orders, speakerSessions }) => { export default connect({ mapStateToProps: (state) => ({ - orders: selectors.getOrders(state), - // TODO: review below + fetchOrderResult: selectors.getOrders(state), + // TODO: review unused code speakerSessions: selectors.getSpeakerSessions(state), }), - component: React.memo(EventList), + component: React.memo(OrderList), });