"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

@@ -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 (
<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 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>
);
@@ -91,7 +109,9 @@ const Subtotal: React.FC<{ amount: number }> = ({ amount }) => {
return (
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
<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>
);
};
@@ -102,7 +122,9 @@ const Shipping: React.FC<{ amount: number }> = ({ amount }) => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
<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>
);
@@ -114,7 +136,9 @@ const Discount: React.FC<{ amount: number }> = ({ amount }) => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
<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>
);
@@ -126,25 +150,25 @@ const Tax: React.FC<{ amount: number }> = ({ amount }) => {
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<div style={{ display: 'flex', gap: '1rem', fontWeight: 'bold' }}>
<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>
);
};
const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
const OrderList: React.FC<SpeakerListProps> = ({ fetchOrderResult, speakerSessions }) => {
const router = useIonRouter();
const [showPopover, setShowPopover] = useState(false);
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
const modal = useRef<HTMLIonModalElement>(null);
useEffect(() => {
// getOrders().then(({ data }) => {
// console.log({ data });
// setEvents(data);
// });
}, []);
const {
result: { status },
data: { orders },
} = fetchOrderResult;
function handleRefresh(event: CustomEvent<RefresherEventDetail>) {
setTimeout(() => {
@@ -165,8 +189,13 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
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.length == 0) return <>order list is empty</>;
return (
<IonPage id="speaker-list">
<IonHeader translucent={true} className="ion-no-border">
@@ -183,7 +212,12 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
<IonContent fullscreen={true}>
<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>
<IonHeader collapse="condense">
@@ -193,17 +227,27 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
</IonHeader>
<IonList>
{orders.map((order, idx) => (
<IonItem button onClick={handleNotImplemented}>
{orders.map((order: IOrderItem, idx: number) => (
<IonItem button onClick={() => handleShowOrderDetail(order.id)} key={idx}>
<div style={{ paddingBottom: '1rem', paddingTop: '1rem' }}>
<div style={{ display: 'flex', gap: '0.5rem', width: 'calc( 100vw - 35px )' }}>
<div style={{}}>
<div>
<div style={{ width: '70px' }}>
<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>
<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} />
<RemainingDays amount={50} />
</div>
@@ -228,8 +272,12 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
}}
>
<div style={{ fontSize: '1.2rem' }}>{order.orderNumber}</div>
<IonButton shape="round" onClick={() => handleShowOrderDetail('1')} size="small" fill="clear">
<IonIcon slot="icon-only" icon={chevronForwardOutline} size="small"></IonIcon>
<IonButton shape="round" size="small" fill="clear">
<IonIcon
slot="icon-only"
icon={chevronForwardOutline}
size="small"
></IonIcon>
</IonButton>
</div>
@@ -272,9 +320,9 @@ const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
export default connect<OwnProps, StateProps, DispatchProps>({
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),
});