"feat: rename 'createdDate' to 'createDate' across frontend, backend, and mobile codebases"
This commit is contained in:
@@ -903,7 +903,7 @@ model InvoiceItem {
|
||||
invoiceTo Json
|
||||
sent Float
|
||||
dueDate DateTime @default(now())
|
||||
createdDate DateTime @default(now())
|
||||
createDate DateTime @default(now())
|
||||
}
|
||||
|
||||
// model Invoice {
|
||||
|
@@ -55,7 +55,7 @@ async function invoiceItem() {
|
||||
invoiceTo: _addressBooks[index + 1],
|
||||
sent: _mock.number.nativeS(index),
|
||||
dueDate: new Date(fAdd({ days: index + 15, hours: index })),
|
||||
createdDate: new Date(fAdd({ days: index + 15, hours: index })),
|
||||
createDate: new Date(fAdd({ days: index + 15, hours: index })),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -2,13 +2,9 @@ import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import prisma from '../../lib/prisma';
|
||||
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
const users = await prisma.user.findMany();
|
||||
|
||||
return response({ users }, STATUS.OK);
|
||||
return response({ hello: 'world' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get latest', error);
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ content-type: application/json
|
||||
"phoneNumber": "+44 20 7946 0958"
|
||||
},
|
||||
"sent": 10,
|
||||
"createdDate": "2025-06-15T17:07:24+08:00",
|
||||
"createDate": "2025-06-15T17:07:24+08:00",
|
||||
"dueDate": "2025-06-15T17:07:24+08:00"
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
set -ex
|
||||
|
||||
# -f docker-compose.db.yml
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml "
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml -f docker-compose.dev.yml"
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES build
|
||||
docker compose $DOCKER_COMPOSE_FILES up -d
|
||||
|
15
03_source/docker/03_mobile.sh
Executable file
15
03_source/docker/03_mobile.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# -f docker-compose.db.yml
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml "
|
||||
|
||||
docker compose $DOCKER_COMPOSE_FILES exec -it mobile bash
|
||||
|
||||
# cd ../api_server
|
||||
# yarn docker:dev
|
||||
# cd ..
|
||||
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES logs -f
|
9
03_source/docker/docker-compose.dev.yml
Normal file
9
03_source/docker/docker-compose.dev.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
frontend:
|
||||
command: "sleep infinity"
|
||||
|
||||
mobile:
|
||||
command: "sleep infinity"
|
||||
|
||||
cms_backend:
|
||||
command: "sleep infinity"
|
@@ -12,8 +12,7 @@ services:
|
||||
volumes:
|
||||
- ../frontend:/app
|
||||
working_dir: "/app"
|
||||
# command: "yarn dev"
|
||||
command: "sleep infinity"
|
||||
command: "yarn dev"
|
||||
|
||||
mobile:
|
||||
image: 192.168.10.61:5000/hksingleparty_mobile
|
||||
@@ -38,8 +37,7 @@ services:
|
||||
volumes:
|
||||
- ../cms_backend:/app
|
||||
working_dir: "/app"
|
||||
# command: "yarn dev"
|
||||
command: "sleep infinity"
|
||||
command: "yarn dev"
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// src/pages/dashboard/kanban/index.tsx
|
||||
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
import { KanbanView } from 'src/sections/kanban/view';
|
||||
|
@@ -186,7 +186,7 @@ export function InvoiceDetails({ invoice }: Props) {
|
||||
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
||||
Date create
|
||||
</Typography>
|
||||
{fDate(invoice?.createdDate)}
|
||||
{fDate(invoice?.createDate)}
|
||||
</Stack>
|
||||
|
||||
<Stack sx={{ typography: 'body2' }}>
|
||||
|
@@ -58,11 +58,11 @@ export const NewInvoiceSchema = zod
|
||||
message: 'Invoice to is required!',
|
||||
}),
|
||||
sent: zod.number().default(0),
|
||||
createdDate: schemaHelper.date({ message: { required: 'Create date is required!' } }),
|
||||
createDate: schemaHelper.date({ message: { required: 'Create date is required!' } }),
|
||||
dueDate: schemaHelper.date({ message: { required: 'Due date is required!' } }),
|
||||
// Not required
|
||||
})
|
||||
.refine((data) => !fIsAfter(data.createdDate, data.dueDate), {
|
||||
.refine((data) => !fIsAfter(data.createDate, data.dueDate), {
|
||||
message: 'Due date cannot be earlier than create date!',
|
||||
path: ['dueDate'],
|
||||
});
|
||||
@@ -82,7 +82,7 @@ export function InvoiceNewEditForm({ currentInvoice }: Props) {
|
||||
|
||||
const defaultValues: NewInvoiceSchemaType = {
|
||||
invoiceNumber: 'INV-1990',
|
||||
createdDate: today(),
|
||||
createDate: today(),
|
||||
dueDate: null,
|
||||
taxes: 0,
|
||||
shipping: 0,
|
||||
|
@@ -139,7 +139,7 @@ function InvoicePdfDocument({ invoice, currentStatus }: InvoicePdfDocumentProps)
|
||||
shipping,
|
||||
subtotal,
|
||||
invoiceTo,
|
||||
createdDate,
|
||||
createDate,
|
||||
totalAmount,
|
||||
invoiceFrom,
|
||||
invoiceNumber,
|
||||
@@ -197,7 +197,7 @@ function InvoicePdfDocument({ invoice, currentStatus }: InvoicePdfDocumentProps)
|
||||
<View style={[styles.container, styles.mb40]}>
|
||||
<View style={{ width: '50%' }}>
|
||||
<Text style={[styles.text1Bold, styles.mb4]}>Date create</Text>
|
||||
<Text style={[styles.text2]}>{fDate(createdDate)}</Text>
|
||||
<Text style={[styles.text2]}>{fDate(createDate)}</Text>
|
||||
</View>
|
||||
<View style={{ width: '50%' }}>
|
||||
<Text style={[styles.text1Bold, styles.mb4]}>Due date</Text>
|
||||
|
@@ -141,8 +141,8 @@ export function InvoiceTableRow({
|
||||
|
||||
<TableCell>
|
||||
<ListItemText
|
||||
primary={fDate(row.createdDate)}
|
||||
secondary={fTime(row.createdDate)}
|
||||
primary={fDate(row.createDate)}
|
||||
secondary={fTime(row.createDate)}
|
||||
slotProps={{
|
||||
primary: { noWrap: true, sx: { typography: 'body2' } },
|
||||
secondary: { sx: { mt: 0.5, typography: 'caption' } },
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// src/sections/kanban/view/kanban-view.tsx
|
||||
|
||||
import type {
|
||||
DragEndEvent,
|
||||
DragOverEvent,
|
||||
|
@@ -32,7 +32,7 @@ export type IInvoiceItem = {
|
||||
dueDate: IDateValue;
|
||||
invoiceNumber: string;
|
||||
items: IInvoiceItemItem[];
|
||||
createdDate: IDateValue;
|
||||
createDate: IDateValue;
|
||||
invoiceTo: IAddressItem;
|
||||
invoiceFrom: IAddressItem;
|
||||
};
|
||||
|
10
03_source/mobile/dev.sh
Executable file
10
03_source/mobile/dev.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
while true; do
|
||||
npm i -D
|
||||
|
||||
npm run dev
|
||||
|
||||
echo "restarting..."
|
||||
sleep 1
|
||||
done
|
@@ -62,6 +62,7 @@ import ChangeLanguage from './pages/ChangeLanguage';
|
||||
import ServiceAgreement from './pages/ServiceAgreement';
|
||||
import paths from './paths';
|
||||
import PrivacyAgreement from './pages/PrivacyAgreement';
|
||||
import AppRoute from './AppRoute';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
@@ -107,8 +108,7 @@ const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn,
|
||||
which makes transitions between tabs and non tab pages smooth
|
||||
*/}
|
||||
|
||||
{/* */}
|
||||
<Route path="/not_implemented" component={NotImplemented} />
|
||||
<AppRoute />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs" render={() => <MainTabs />} />
|
||||
@@ -118,8 +118,6 @@ const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn,
|
||||
<Route path="/support" component={Support} />
|
||||
<Route path="/tutorial" component={Tutorial} />
|
||||
{/* */}
|
||||
<Route path="/event_detail/:id" component={EventDetail} />
|
||||
<Route path="/profile/:id" component={MemberProfile} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.SETTINGS} component={Settings} />
|
||||
|
18
03_source/mobile/src/AppRoute.tsx
Normal file
18
03_source/mobile/src/AppRoute.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Route } from 'react-router';
|
||||
import NotImplemented from './pages/NotImplemented';
|
||||
import EventDetail from './pages/EventDetail';
|
||||
import MemberProfile from './pages/MemberProfile';
|
||||
|
||||
const AppRoute: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Route path="/not_implemented" component={NotImplemented} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/event_detail/:id" component={EventDetail} />
|
||||
<Route path="/profile/:id" component={MemberProfile} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppRoute;
|
39
03_source/mobile/src/TabAppRoute.tsx
Normal file
39
03_source/mobile/src/TabAppRoute.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Route } from 'react-router';
|
||||
import NotImplemented from './pages/NotImplemented';
|
||||
import EventDetail from './pages/EventDetail';
|
||||
import MemberProfile from './pages/MemberProfile';
|
||||
import paths from './paths';
|
||||
import MembersNearByList from './pages/MembersNearByList';
|
||||
import OrderList from './pages/OrderList';
|
||||
import MessageList from './pages/MessageList';
|
||||
import Favourites from './pages/Favourites';
|
||||
import MyProfile from './pages/MyProfile';
|
||||
import EventList from './pages/EventList';
|
||||
|
||||
const TabAppRoute: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Route path="/tabs/not_implemented" component={NotImplemented} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.NEARBY_LIST} render={() => <MembersNearByList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.ORDERS_LIST} render={() => <OrderList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.MESSAGE_LIST} render={() => <MessageList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.FAVOURITES_LIST} render={() => <Favourites />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs/events" render={() => <EventList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs/my_profile" render={() => <MyProfile />} exact={true} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabAppRoute;
|
@@ -16,8 +16,9 @@ export const getConfData = async () => {
|
||||
const response = await Promise.all([
|
||||
fetch(dataUrl),
|
||||
fetch(locationsUrl),
|
||||
axios.get(`${constants.API_ENDPOINT}/v1/events`),
|
||||
axios.get(`${constants.API_ENDPOINT}/v1/members`),
|
||||
axios.get(`${constants.API_ENDPOINT}/api/order/list`),
|
||||
// axios.get(`${constants.API_ENDPOINT}/v1/events`),
|
||||
// axios.get(`${constants.API_ENDPOINT}/v1/members`),
|
||||
//
|
||||
]);
|
||||
const responseData = await response[0].json();
|
||||
@@ -30,8 +31,12 @@ export const getConfData = async () => {
|
||||
.filter((trackName, index, array) => array.indexOf(trackName) === index)
|
||||
.sort();
|
||||
|
||||
const events = response[2].data;
|
||||
const nearByMembers = response[3].data;
|
||||
// const events = response[2].data;
|
||||
// const nearByMembers = response[3].data;
|
||||
const orders = response[2].data.orders;
|
||||
|
||||
const events = [];
|
||||
const nearByMembers = [];
|
||||
|
||||
const data = {
|
||||
schedule,
|
||||
@@ -43,16 +48,14 @@ export const getConfData = async () => {
|
||||
//
|
||||
events,
|
||||
nearByMembers,
|
||||
orders,
|
||||
//
|
||||
};
|
||||
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;
|
||||
|
@@ -19,144 +19,116 @@ const getSearchText = (state: AppState) => state.data.searchText;
|
||||
|
||||
export const getEvents = (state: AppState) => 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 getFilteredSchedule = createSelector(getSchedule, getFilteredTracks, (schedule, filteredTracks) => {
|
||||
const groups: ScheduleGroup[] = [];
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
if (period === 'pm' && hours !== 12) {
|
||||
hours += 12;
|
||||
} else if (period === 'am' && hours === 12) {
|
||||
hours = 0;
|
||||
}
|
||||
if (period === 'pm' && hours !== 12) {
|
||||
hours += 12;
|
||||
} else if (period === 'am' && hours === 12) {
|
||||
hours = 0;
|
||||
}
|
||||
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes || '00'}`;
|
||||
};
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes || '00'}`;
|
||||
};
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
});
|
||||
|
||||
sortedGroups.forEach((group: ScheduleGroup) => {
|
||||
const sessions: Session[] = [];
|
||||
group.sessions.forEach((session) => {
|
||||
session.tracks.forEach((track) => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
sessions.push(session);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
sortedGroups.forEach((group: ScheduleGroup) => {
|
||||
const sessions: Session[] = [];
|
||||
group.sessions.forEach((session) => {
|
||||
session.tracks.forEach((track) => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
sessions.push(session);
|
||||
}
|
||||
});
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
const groupToAdd: ScheduleGroup = {
|
||||
time: group.time,
|
||||
sessions: sortedSessions,
|
||||
};
|
||||
groups.push(groupToAdd);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
date: schedule.date,
|
||||
groups,
|
||||
} as Schedule;
|
||||
}
|
||||
);
|
||||
|
||||
export const getSearchedSchedule = createSelector(
|
||||
getFilteredSchedule,
|
||||
getSearchText,
|
||||
(schedule, searchText) => {
|
||||
if (!searchText) {
|
||||
return schedule;
|
||||
const groupToAdd: ScheduleGroup = {
|
||||
time: group.time,
|
||||
sessions: sortedSessions,
|
||||
};
|
||||
groups.push(groupToAdd);
|
||||
}
|
||||
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
|
||||
);
|
||||
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;
|
||||
export const getSearchedSchedule = createSelector(getFilteredSchedule, getSearchText, (schedule, searchText) => {
|
||||
if (!searchText) {
|
||||
return 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;
|
||||
});
|
||||
|
||||
const getIdParam = (_state: AppState, props: any) => {
|
||||
return props.match.params['id'];
|
||||
};
|
||||
|
||||
export const getSession = createSelector(
|
||||
getSessions,
|
||||
getIdParam,
|
||||
(sessions, id) => {
|
||||
return sessions.find((s: Session) => s.id === id);
|
||||
}
|
||||
);
|
||||
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, (events, id) => events.find((x: Event) => x.id === id));
|
||||
|
||||
export const getSpeakerSessions = createSelector(getSessions, (sessions) => {
|
||||
const speakerSessions: { [key: string]: Session[] } = {};
|
||||
@@ -175,9 +147,7 @@ export const getSpeakerSessions = createSelector(getSessions, (sessions) => {
|
||||
});
|
||||
|
||||
export const mapCenter = (state: AppState) => {
|
||||
const item = state.data.locations.find(
|
||||
(l: Location) => l.id === state.data.mapCenterId
|
||||
);
|
||||
const item = state.data.locations.find((l: Location) => l.id === state.data.mapCenterId);
|
||||
if (item == null) {
|
||||
return {
|
||||
id: 1,
|
||||
|
61
03_source/mobile/src/data/sessions/orders.actions.ts
Normal file
61
03_source/mobile/src/data/sessions/orders.actions.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { getConfData } from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { ConfState } from './conf.state';
|
||||
|
||||
export const loadConfData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getConfData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-conf-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<ConfState>) =>
|
||||
({
|
||||
type: 'set-conf-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const addFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'add-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const removeFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'remove-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const updateFilteredTracks = (filteredTracks: string[]) =>
|
||||
({
|
||||
type: 'update-filtered-tracks',
|
||||
filteredTracks,
|
||||
} as const);
|
||||
|
||||
export const setSearchText = (searchText?: string) =>
|
||||
({
|
||||
type: 'set-search-text',
|
||||
searchText,
|
||||
} as const);
|
||||
|
||||
export const setMenuEnabled = (menuEnabled: boolean) =>
|
||||
({
|
||||
type: 'set-menu-enabled',
|
||||
menuEnabled,
|
||||
} as const);
|
||||
|
||||
export type OrdersActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof addFavorite>
|
||||
| ActionType<typeof removeFavorite>
|
||||
| ActionType<typeof updateFilteredTracks>
|
||||
| ActionType<typeof setSearchText>
|
||||
| ActionType<typeof setMenuEnabled>;
|
31
03_source/mobile/src/data/sessions/orders.reducer.ts
Normal file
31
03_source/mobile/src/data/sessions/orders.reducer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { OrdersActions } from './orders.actions';
|
||||
import { ConfState } from './conf.state';
|
||||
|
||||
export const orderReducer = (state: ConfState, action: OrdersActions): ConfState => {
|
||||
switch (action.type) {
|
||||
case 'set-conf-loading': {
|
||||
return { ...state, loading: action.isLoading };
|
||||
}
|
||||
case 'set-conf-data': {
|
||||
return { ...state, ...action.data };
|
||||
}
|
||||
case 'add-favorite': {
|
||||
return { ...state, favorites: [...state.favorites, action.sessionId] };
|
||||
}
|
||||
case 'remove-favorite': {
|
||||
return {
|
||||
...state,
|
||||
favorites: [...state.favorites.filter((x) => x !== action.sessionId)],
|
||||
};
|
||||
}
|
||||
case 'update-filtered-tracks': {
|
||||
return { ...state, filteredTracks: action.filteredTracks };
|
||||
}
|
||||
case 'set-search-text': {
|
||||
return { ...state, searchText: action.searchText };
|
||||
}
|
||||
case 'set-menu-enabled': {
|
||||
return { ...state, menuEnabled: action.menuEnabled };
|
||||
}
|
||||
}
|
||||
};
|
@@ -3,6 +3,8 @@ import { combineReducers } from './combineReducers';
|
||||
import { sessionsReducer } from './sessions/sessions.reducer';
|
||||
import { userReducer } from './user/user.reducer';
|
||||
import { locationsReducer } from './locations/locations.reducer';
|
||||
//
|
||||
import { orderReducer } from './sessions/orders.reducer';
|
||||
|
||||
export const initialState: AppState = {
|
||||
data: {
|
||||
@@ -19,6 +21,7 @@ export const initialState: AppState = {
|
||||
//
|
||||
events: [],
|
||||
nearbyMembers: [],
|
||||
orders: [],
|
||||
},
|
||||
user: {
|
||||
hasSeenTutorial: false,
|
||||
@@ -35,6 +38,8 @@ export const reducers = combineReducers({
|
||||
data: sessionsReducer,
|
||||
user: userReducer,
|
||||
locations: locationsReducer,
|
||||
//
|
||||
order: orderReducer,
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof reducers>;
|
||||
|
15
03_source/mobile/src/models/Order.ts
Normal file
15
03_source/mobile/src/models/Order.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export type IDateValue = string | number | null;
|
||||
|
||||
export interface Order {
|
||||
id: string;
|
||||
createdAt: IDateValue;
|
||||
//
|
||||
taxes: number;
|
||||
status: string;
|
||||
shipping: number;
|
||||
discount: number;
|
||||
subtotal: number;
|
||||
orderNumber: string;
|
||||
totalAmount: number;
|
||||
totalQuantity: number;
|
||||
}
|
@@ -17,6 +17,7 @@ import MyProfile from './MyProfile';
|
||||
import MessageList from './MessageList';
|
||||
import paths from '../paths';
|
||||
import Favourites from './Favourites';
|
||||
import TabAppRoute from '../TabAppRoute';
|
||||
|
||||
interface MainTabsProps {}
|
||||
|
||||
@@ -40,22 +41,7 @@ const MainTabs: React.FC<MainTabsProps> = () => {
|
||||
<Route path="/tabs/about" render={() => <About />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs/events" render={() => <EventList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.NEARBY_LIST} render={() => <MembersNearByList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.ORDERS_LIST} render={() => <OrderList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.MESSAGE_LIST} render={() => <MessageList />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path={paths.FAVOURITES_LIST} render={() => <Favourites />} exact={true} />
|
||||
|
||||
{/* */}
|
||||
<Route path="/tabs/my_profile" render={() => <MyProfile />} exact={true} />
|
||||
<TabAppRoute />
|
||||
</IonRouterOutlet>
|
||||
{/* */}
|
||||
<IonTabBar slot="bottom">
|
||||
|
155
03_source/mobile/src/pages/MyProfile/NotLoggedIn/index.tsx
Normal file
155
03_source/mobile/src/pages/MyProfile/NotLoggedIn/index.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
// REQ0053/profile-page
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Provides functionality view user profile
|
||||
//
|
||||
// RULES:
|
||||
// - T.B.A.
|
||||
//
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonPage,
|
||||
IonButtons,
|
||||
IonMenuButton,
|
||||
IonGrid,
|
||||
IonRow,
|
||||
IonCol,
|
||||
useIonRouter,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonPopover,
|
||||
IonAvatar,
|
||||
IonImg,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonModal,
|
||||
IonSearchbar,
|
||||
useIonModal,
|
||||
IonInput,
|
||||
RefresherEventDetail,
|
||||
IonRefresher,
|
||||
IonRefresherContent,
|
||||
} 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 { alertOutline, chevronDownCircleOutline, createOutline, heart, menuOutline, settingsOutline } from 'ionicons/icons';
|
||||
import AboutPopover from '../../../components/AboutPopover';
|
||||
import paths from '../../../paths';
|
||||
import { getProfileById } from '../../../api/getProfileById';
|
||||
import { defaultMember, Member } from '../../MemberProfile/type';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
interface StateProps {
|
||||
speakers: Speaker[];
|
||||
speakerSessions: { [key: string]: Session[] };
|
||||
}
|
||||
|
||||
interface DispatchProps {}
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
const [profile, setProfile] = useState<Member>(defaultMember);
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
const router = useIonRouter();
|
||||
|
||||
function handleShowSettingButtonClick() {
|
||||
router.push(paths.SETTINGS);
|
||||
}
|
||||
|
||||
function handleNotImplementedClick() {
|
||||
router.push(paths.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
function handleRefresh(event: CustomEvent<RefresherEventDetail>) {
|
||||
setTimeout(() => {
|
||||
// Any calls to load data go here
|
||||
event.detail.complete();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
const [disableForwardLoginButton, setDisableForwardLoginButton] = useState(false);
|
||||
function handleForwardLoginPage() {
|
||||
try {
|
||||
setDisableForwardLoginButton(true);
|
||||
router.push(paths.login);
|
||||
setDisableForwardLoginButton(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getProfileById('2').then(({ data }) => {
|
||||
console.log({ data });
|
||||
setProfile(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!profile) return <>loading</>;
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader translucent={true} className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="end">
|
||||
{/* <IonMenuButton /> */}
|
||||
<IonButton shape="round" onClick={() => handleShowSettingButtonClick()}>
|
||||
<IonIcon slot="icon-only" icon={settingsOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle>My profile</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen={true}>
|
||||
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
||||
<IonRefresherContent pullingIcon={chevronDownCircleOutline} pullingText="Pull to refresh" refreshingSpinner="circles" refreshingText="Refreshing..."></IonRefresherContent>
|
||||
</IonRefresher>
|
||||
|
||||
<IonHeader collapse="condense" className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">My profile</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<div style={{ height: '50vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div>
|
||||
not login yet, <br />
|
||||
please login or sign up
|
||||
</div>
|
||||
<div style={{ height: '50vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<IonButton disabled={disableForwardLoginButton} onClick={handleForwardLoginPage}>
|
||||
Login
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state),
|
||||
}),
|
||||
component: React.memo(MyProfile),
|
||||
});
|
@@ -49,6 +49,7 @@ import AboutPopover from '../../components/AboutPopover';
|
||||
import paths from '../../paths';
|
||||
import { getProfileById } from '../../api/getProfileById';
|
||||
import { defaultMember, Member } from '../MemberProfile/type';
|
||||
import NotLoggedIn from './NotLoggedIn';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@@ -63,6 +64,7 @@ interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
const [profile, setProfile] = useState<Member>(defaultMember);
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
@@ -92,6 +94,7 @@ const MyProfile: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
}, []);
|
||||
|
||||
if (!profile) return <>loading</>;
|
||||
if (profile.id == -1) return <NotLoggedIn />;
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
|
@@ -7,34 +7,9 @@
|
||||
// - 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,
|
||||
} from '@ionic/react';
|
||||
import { IonHeader, IonToolbar, IonContent, IonPage, IonButtons, IonMenuButton, IonButton, IonIcon, IonDatetime, IonSelectOption, IonList, IonItem, IonLabel, IonSelect, IonPopover, IonText, IonFooter, useIonRouter } from '@ionic/react';
|
||||
import './style.scss';
|
||||
import {
|
||||
chevronBackOutline,
|
||||
ellipsisHorizontal,
|
||||
ellipsisVertical,
|
||||
heart,
|
||||
logoIonic,
|
||||
} from 'ionicons/icons';
|
||||
import { chevronBackOutline, ellipsisHorizontal, ellipsisVertical, heart, logoIonic } from 'ionicons/icons';
|
||||
import AboutPopover from '../../components/AboutPopover';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { TestContent } from './TestContent';
|
||||
@@ -60,12 +35,8 @@ interface Event {
|
||||
const EventDetail: React.FC<AboutProps> = () => {
|
||||
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 [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',
|
||||
@@ -95,8 +66,6 @@ const EventDetail: React.FC<AboutProps> = () => {
|
||||
router.goBack();
|
||||
}
|
||||
|
||||
if (!eventDetail) return <>loading</>;
|
||||
|
||||
return (
|
||||
<IonPage id="about-page">
|
||||
<IonContent>
|
||||
@@ -110,11 +79,7 @@ const EventDetail: React.FC<AboutProps> = () => {
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={presentPopover}>
|
||||
<IonIcon
|
||||
slot="icon-only"
|
||||
ios={ellipsisHorizontal}
|
||||
md={ellipsisVertical}
|
||||
></IonIcon>
|
||||
<IonIcon slot="icon-only" ios={ellipsisHorizontal} md={ellipsisVertical}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
@@ -143,7 +108,8 @@ const EventDetail: React.FC<AboutProps> = () => {
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
></div>
|
||||
Sorry but Not implemented
|
||||
Sorry this feature
|
||||
<br /> Not implemented
|
||||
<IonButton onClick={handleBackOnClick} fill="clear" size="large">
|
||||
<IonIcon icon={chevronBackOutline} slot="start"></IonIcon>
|
||||
Back
|
||||
|
@@ -38,17 +38,11 @@ import '../SpeakerList.scss';
|
||||
import { getEvents } from '../../api/getEvents';
|
||||
import { format } from 'date-fns';
|
||||
import { Order } from './types';
|
||||
import {
|
||||
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';
|
||||
import paths from '../../paths';
|
||||
|
||||
interface OwnProps {}
|
||||
|
||||
@@ -61,17 +55,14 @@ interface DispatchProps {}
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const EventList: React.FC<SpeakerListProps> = ({
|
||||
speakers,
|
||||
speakerSessions,
|
||||
}) => {
|
||||
const [orders, setEvents] = useState<Order[] | []>([]);
|
||||
const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
const router = useIonRouter();
|
||||
const [orders, setEvents] = useState<Order[]>([]);
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
const router = useIonRouter();
|
||||
|
||||
useEffect(() => {
|
||||
getOrders().then(({ data }) => {
|
||||
console.log({ data });
|
||||
@@ -86,8 +77,16 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function handleShowOrderDetail(event_id: string) {
|
||||
router.push(`/order_detail/${event_id}`);
|
||||
function handleShowOrderDetail(order_id: string) {
|
||||
router.push(paths.getOrderDetail(order_id));
|
||||
}
|
||||
|
||||
function handleNotImplemented() {
|
||||
router.push(paths.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
function handleBookmarksClick() {
|
||||
router.push(paths.FAVOURITES_LIST);
|
||||
}
|
||||
|
||||
if (!orders) return <Loading />;
|
||||
@@ -96,18 +95,19 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader translucent={true} className="ion-no-border">
|
||||
<IonToolbar>
|
||||
<IonButtons slot="end">
|
||||
{/* <IonMenuButton /> */}
|
||||
<IonButton shape="round" expand="block" onClick={handleBookmarksClick}>
|
||||
<IonIcon slot="icon-only" icon={bookmarksOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle>My Orders</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<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">
|
||||
@@ -116,94 +116,73 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid fixed>
|
||||
<IonRow>
|
||||
{orders.map((order, idx) => (
|
||||
<IonCol size="12" size-md="6" key={idx}>
|
||||
<div>
|
||||
<IonList>
|
||||
{orders.map((order, idx) => (
|
||||
<IonItem button onClick={handleNotImplemented}>
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: '1rem',
|
||||
paddingTop: '1rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '1rem',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '70px' }}>
|
||||
<IonAvatar>
|
||||
<img alt="Silhouette of a person's head" src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc" />
|
||||
</IonAvatar>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '70px' }}>
|
||||
<IonAvatar>
|
||||
<img
|
||||
alt="Silhouette of a person's head"
|
||||
src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc"
|
||||
/>
|
||||
</IonAvatar>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '1.2rem' }}>{order.title}</div>
|
||||
<IonButton shape="round" onClick={() => handleShowOrderDetail('1')} size="small" fill="clear">
|
||||
<IonIcon slot="icon-only" icon={chevronForwardOutline} size="small"></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
flexGrow: 1,
|
||||
gap: '0.33rem',
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '1.2rem' }}>{order.title}</div>
|
||||
<IonButton
|
||||
shape="round"
|
||||
onClick={() => handleShowOrderDetail('1')}
|
||||
size="small"
|
||||
fill="clear"
|
||||
>
|
||||
<IonIcon
|
||||
slot="icon-only"
|
||||
icon={chevronForwardOutline}
|
||||
size="small"
|
||||
></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.33rem',
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
>
|
||||
<div>Order time: {order.createdAt} </div>
|
||||
<div>Last payment date: {order.last_payment_date}</div>
|
||||
<div>Number of applicants: 38 </div>
|
||||
<div>Remaining dates: 50 </div>
|
||||
<div>
|
||||
<IonButton fill="outline">{order.status}</IonButton>
|
||||
</div>
|
||||
<div>Order time: {order.createdAt} </div>
|
||||
<div>Last payment date: {order.last_payment_date}</div>
|
||||
<div>Number of applicants: 38 </div>
|
||||
<div>Remaining dates: 50 </div>
|
||||
<div>
|
||||
<IonButton fill="outline">{order.status}</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
borderBottom: '1px solid lightgray',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
></div>
|
||||
</IonCol>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</div>
|
||||
</IonItem>
|
||||
))}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
{/* REQ0079/event-filter */}
|
||||
<IonModal
|
||||
ref={modal}
|
||||
trigger="open-modal"
|
||||
initialBreakpoint={0.5}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
>
|
||||
<IonModal ref={modal} trigger="open-modal" initialBreakpoint={0.5} breakpoints={[0, 0.25, 0.5, 0.75]}>
|
||||
<IonContent className="ion-padding">
|
||||
<div
|
||||
style={{
|
||||
@@ -255,10 +234,7 @@ const EventList: React.FC<SpeakerListProps> = ({
|
||||
<IonButton shape="round">All</IonButton>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
shape="round"
|
||||
style={{ marginTop: '1rem', marginBottom: '1rem' }}
|
||||
>
|
||||
<IonButton shape="round" style={{ marginTop: '1rem', marginBottom: '1rem' }}>
|
||||
Apply
|
||||
</IonButton>
|
||||
</div>
|
||||
|
@@ -31,13 +31,14 @@ import {
|
||||
} from '@ionic/react';
|
||||
import SpeakerItem from '../../components/SpeakerItem';
|
||||
import { Speaker } from '../../models/Speaker';
|
||||
import { Order } from '../../models/Order';
|
||||
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 { Order } from './types';
|
||||
// import { Order } from './types';
|
||||
import { bookmarksOutline, chevronBackOutline, chevronDownCircleOutline, chevronForwardOutline, heart, logoIonic, menuOutline } from 'ionicons/icons';
|
||||
import AboutPopover from '../../components/AboutPopover';
|
||||
import { getOrders } from '../../api/getOrders';
|
||||
@@ -47,7 +48,8 @@ import paths from '../../paths';
|
||||
interface OwnProps {}
|
||||
|
||||
interface StateProps {
|
||||
speakers: Speaker[];
|
||||
orders: Order[];
|
||||
//
|
||||
speakerSessions: { [key: string]: Session[] };
|
||||
}
|
||||
|
||||
@@ -55,19 +57,93 @@ interface DispatchProps {}
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps {}
|
||||
|
||||
const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
const RemainingDays: React.FC<{ amount: number }> = ({ amount }) => {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>#Rem:</div>
|
||||
<div>{amount}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NumApplicants: React.FC<{ amount: number }> = ({ amount }) => {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>#app. :</div>
|
||||
<div>{amount}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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>Total:</div>
|
||||
<div style={{ minWidth: '75px', display: 'inline-flex', justifyContent: 'flex-end' }}>{amount} </div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Subtotal HK$83.74
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
// Shipping - HK$10
|
||||
const Shipping: React.FC<{ amount: number }> = ({ amount }) => {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Discount - HK$10
|
||||
const Discount: React.FC<{ amount: number }> = ({ amount }) => {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Taxes HK$10
|
||||
const Tax: React.FC<{ amount: number }> = ({ amount }) => {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EventList: React.FC<SpeakerListProps> = ({ orders, speakerSessions }) => {
|
||||
const router = useIonRouter();
|
||||
const [orders, setEvents] = useState<Order[] | []>([]);
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getOrders().then(({ data }) => {
|
||||
console.log({ data });
|
||||
setEvents(data);
|
||||
});
|
||||
// getOrders().then(({ data }) => {
|
||||
// console.log({ data });
|
||||
// setEvents(data);
|
||||
// });
|
||||
}, []);
|
||||
|
||||
function handleRefresh(event: CustomEvent<RefresherEventDetail>) {
|
||||
@@ -119,58 +195,68 @@ const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
<IonList>
|
||||
{orders.map((order, idx) => (
|
||||
<IonItem button onClick={handleNotImplemented}>
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: '1rem',
|
||||
paddingTop: '1rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '1rem',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '70px' }}>
|
||||
<IonAvatar>
|
||||
<img alt="Silhouette of a person's head" src="https://plus.unsplash.com/premium_photo-1683121126477-17ef068309bc" />
|
||||
</IonAvatar>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '1.2rem' }}>{order.title}</div>
|
||||
<IonButton shape="round" onClick={() => handleShowOrderDetail('1')} size="small" fill="clear">
|
||||
<IonIcon slot="icon-only" icon={chevronForwardOutline} size="small"></IonIcon>
|
||||
</IonButton>
|
||||
<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" />
|
||||
</IonAvatar>
|
||||
<div style={{ marginTop: '1rem', display: 'inline-flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||
<NumApplicants amount={38} />
|
||||
<RemainingDays amount={50} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<div
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
//
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.33rem',
|
||||
fontSize: '0.9rem',
|
||||
gap: '1rem',
|
||||
}}
|
||||
>
|
||||
<div>Order time: {order.createdAt} </div>
|
||||
<div>Last payment date: {order.last_payment_date}</div>
|
||||
<div>Number of applicants: 38 </div>
|
||||
<div>Remaining dates: 50 </div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.33rem',
|
||||
fontSize: '0.9rem',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'inline-flex', gap: '0.5rem' }}>
|
||||
<div style={{ fontWeight: 'bold' }}>Order time:</div>
|
||||
<div>{order.createdAt.split('T')[0]}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 'bold' }}>Last payment date:</div>
|
||||
<div>{order.last_payment_date}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<IonButton fill="outline">{order.status}</IonButton>
|
||||
<Subtotal amount={order.subtotal} />
|
||||
<Shipping amount={order.shipping} />
|
||||
<Discount amount={order.discount} />
|
||||
<Tax amount={order.taxes} />
|
||||
<TotalAmount amount={order.totalAmount} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,73 +266,14 @@ const EventList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) =>
|
||||
))}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
{/* REQ0079/event-filter */}
|
||||
<IonModal ref={modal} trigger="open-modal" initialBreakpoint={0.5} breakpoints={[0, 0.25, 0.5, 0.75]}>
|
||||
<IonContent className="ion-padding">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontWeight: '1rem',
|
||||
fontSize: '1.5rem',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontWeight: '1rem',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
>
|
||||
Maximum number of participant
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<IonButton shape="round">2-10</IonButton>
|
||||
<IonButton shape="round">12-40</IonButton>
|
||||
<IonButton shape="round">All</IonButton>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontWeight: '1rem',
|
||||
marginTop: '0.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}}
|
||||
>
|
||||
Held date
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<IonButton shape="round">Weekly</IonButton>
|
||||
<IonButton shape="round">Monthly</IonButton>
|
||||
<IonButton shape="round">All</IonButton>
|
||||
</div>
|
||||
|
||||
<IonButton shape="round" style={{ marginTop: '1rem', marginBottom: '1rem' }}>
|
||||
Apply
|
||||
</IonButton>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
orders: selectors.getOrders(state),
|
||||
// TODO: review below
|
||||
speakerSessions: selectors.getSpeakerSessions(state),
|
||||
}),
|
||||
component: React.memo(EventList),
|
||||
|
@@ -11,5 +11,7 @@ const paths = {
|
||||
CHANGE_LANGUAGE: '/change_language',
|
||||
SERVICE_AGREEMENT: '/service_agreement',
|
||||
PRIVACY_AGREEMENT: '/privacy_agreement',
|
||||
//
|
||||
login: '/login',
|
||||
};
|
||||
export default paths;
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user