Integrasi API: Event Qr Code

Fix:
- app/(application)/(user)/event/[id]/confirmation.tsx
- app/(application)/(user)/event/[id]/list-of-participants.tsx
- app/(application)/admin/event/[id]/[status]/index.tsx
- app/(application)/admin/event/[id]/list-of-participants.tsx
- components/DateInput/DataTimeAndroid.tsx
- components/DateInput/DateTimeIOS.tsx
- service/api-admin/api-admin-event.ts

### No Issue
This commit is contained in:
2025-10-24 11:57:05 +08:00
parent 36dbfa3296
commit 1e0b72de22
7 changed files with 202 additions and 77 deletions

View File

@@ -74,8 +74,6 @@ export default function UserEventConfirmation() {
userId: user?.id as string, userId: user?.id as string,
}); });
console.log("[RES CONFIRMATION]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data?.dataEvent); setData(response.data?.dataEvent);
setPeserta(response.data?.peserta); setPeserta(response.data?.peserta);
@@ -142,11 +140,11 @@ export default function UserEventConfirmation() {
return ( return (
<TamplateBox data={data}> <TamplateBox data={data}>
<TamplateText <TamplateText
text={`Event telah selesai, anda terdaftar sebagai peserta dan${ text={`Event telah selesai, anda terdaftar sebagai peserta dan ${
konfirmasi konfirmasi
? "Anda telah mengonfirmasi kehadiran." ? "Anda telah mengonfirmasi kehadiran."
: "Anda tidak mengonfirmasi kehadiran." : "Anda tidak mengonfirmasi kehadiran."
}. Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`} } Terima kasih atas perhatian dan minat Anda. Kami berharap dapat bertemu di acara kami berikutnya.`}
/> />
<BackToOtherPath <BackToOtherPath
path="event" path="event"
@@ -173,14 +171,16 @@ export default function UserEventConfirmation() {
if (isWithinConfirmationWindow && peserta === true) { if (isWithinConfirmationWindow && peserta === true) {
if (konfirmasi === false) { if (konfirmasi === false) {
return ( return (
<TamplateBox data={data}> <UserParticipan_And_DuringEvent
<TamplateText text="Konfirmasi Kehadiran" /> id={data.id}
</TamplateBox> userId={user?.id as string}
data={data}
/>
); );
} }
return ( return (
<TamplateBox data={data}> <TamplateBox data={data}>
<TamplateText text="Anda telah mengonfirmasi kehadiran." /> <TamplateText text="Terimakasih telah mengonfirmasi kehadiran. Silahkan lihat peserta lain pada halaman event atau kembali ke halaman home. Selamat menikmati acara dan selamat berpartisipasi." />
<BackToOtherPath <BackToOtherPath
path="event" path="event"
id={data.id} id={data.id}
@@ -192,7 +192,7 @@ export default function UserEventConfirmation() {
return ( return (
<TamplateBox data={data}> <TamplateBox data={data}>
<TamplateText text="Anda terdaftar sebagai peserta. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." /> <TamplateText text="Anda telah terdaftar sebagai peserta pada Event ini. Konfirmasi kehadiran dibuka 1 jam sebelum acara dimulai." />
<BackToOtherPath <BackToOtherPath
path="event" path="event"
id={data.id} id={data.id}
@@ -326,7 +326,7 @@ const TamplateBox = ({
); );
}; };
const TamplateText = ({ text }: { text: string }) => { const TamplateText = ({ text }: { text: React.ReactNode }) => {
return ( return (
<> <>
<TextCustom align="center">{text}</TextCustom> <TextCustom align="center">{text}</TextCustom>
@@ -442,7 +442,7 @@ const NotStarted_And_UserNotParticipan = ({
}; };
// 🟡 ZONA ACARA BERLANGSUNG // 🟡 ZONA ACARA BERLANGSUNG
// Acara sedang berlangsung & belum terdaftar // Acara sedang berlangsung & belum terdaftar & user harus join dan konfirmasi
const UserNotParticipan_And_DuringEvent = ({ const UserNotParticipan_And_DuringEvent = ({
id, id,
userId, userId,
@@ -464,8 +464,6 @@ const UserNotParticipan_And_DuringEvent = ({
category: "join_and_confirm", category: "join_and_confirm",
}); });
// console.log("[RES JOIN & CONFIRMATION EVENT]", response);
if (!response.success) { if (!response.success) {
Toast.show({ Toast.show({
type: "error", type: "error",
@@ -498,3 +496,59 @@ const UserNotParticipan_And_DuringEvent = ({
</> </>
); );
}; };
// 🟡 ZONA ACARA BERLANGSUN
// User sudah terdaftar & Event sedang berlangsung & user harus konfirmasi
const UserParticipan_And_DuringEvent = ({
id,
userId,
data,
}: {
id: string;
userId: string;
data: DataEvent;
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const response = await apiEventConfirmationAction({
id: id as string,
userId: userId as string,
category: "confirmation",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Anda gagal konfirmasi",
});
return;
}
Toast.show({
type: "success",
text1: "Anda berhasil konfirmasi",
});
router.navigate(`/(application)/(user)/event/${id}/publish`);
} catch (error) {
console.log("[ERROR JOIN & CONFIRMATION EVENT]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
<TamplateBox data={data}>
<TamplateText text="Anda sudah terdaftar sebagai peserta & Event sedang berlangsung. Silahkan konfirmasi kehadiran" />
<ButtonCustom onPress={() => handlerSubmit()} isLoading={isLoading}>
Konfirmasi
</ButtonCustom>
</TamplateBox>
</>
);
};

View File

@@ -11,29 +11,30 @@ import {
apiEventGetOne, apiEventGetOne,
apiEventListOfParticipants, apiEventListOfParticipants,
} from "@/service/api-client/api-event"; } from "@/service/api-client/api-event";
import { useLocalSearchParams } from "expo-router"; import dayjs, { Dayjs } from "dayjs";
import { useEffect, useState } from "react"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function EventListOfParticipants() { export default function EventListOfParticipants() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [startDate, setStartDate] = useState(); const [startDate, setStartDate] = useState<Dayjs | undefined>();
const [listData, setListData] = useState([]); const [listData, setListData] = useState<any[] | null>(null);
const [isLoadData, setIsLoadData] = useState(false); const [loadtData, setLoadData] = useState(false);
useEffect(() => { useFocusEffect(
handlerLoadData(); useCallback(() => {
}, [id]); handlerLoadData();
}, [id])
);
const handlerLoadData = () => { const handlerLoadData = () => {
try { try {
setIsLoadData(true);
onLoadData(); onLoadData();
onLoadList(); onLoadList();
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
} }
}; };
@@ -41,7 +42,8 @@ export default function EventListOfParticipants() {
try { try {
const response = await apiEventGetOne({ id: id as string }); const response = await apiEventGetOne({ id: id as string });
if (response.success) { if (response.success) {
setStartDate(response.data.tanggal); const date = dayjs(response.data.tanggal);
setStartDate(date);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
@@ -50,30 +52,36 @@ export default function EventListOfParticipants() {
const onLoadList = async () => { const onLoadList = async () => {
try { try {
setLoadData(true);
const response = await apiEventListOfParticipants({ id: id as string }); const response = await apiEventListOfParticipants({ id: id as string });
if (response.success) { if (response.success) {
setListData(response.data); setListData(response.data);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
}; };
return ( return (
<ViewWrapper> <ViewWrapper>
{isLoadData ? ( {loadtData && !listData ? (
<LoaderCustom /> <LoaderCustom />
) : listData.length === 0 ? ( ) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada peserta</TextCustom> <TextCustom align="center" color="gray">
Belum ada peserta
</TextCustom>
) : ( ) : (
listData.map((item: any, index: number) => ( listData?.map((item: any, index: number) => (
<BaseBox key={index}> <BaseBox key={index}>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId} avatar={item?.User?.Profile?.imageId}
name={item?.User?.username} name={item?.User?.username}
avatarHref={`/profile/${item?.User?.Profile?.id}`} avatarHref={`/profile/${item?.User?.Profile?.id}`}
rightComponent={ rightComponent={
new Date().getTime() > new Date(startDate as any).getTime() ? ( startDate && startDate.subtract(1, "hour").diff(dayjs()) < 0 ? (
<View <View
style={{ style={{
justifyContent: "flex-end", justifyContent: "flex-end",

View File

@@ -5,6 +5,7 @@ import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom, StackCustom,
@@ -28,18 +29,15 @@ import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import QRCode from "react-native-qrcode-svg"; import QRCode from "react-native-qrcode-svg";
import Toast from "react-native-toast-message";
export default function AdminEventDetail() { export default function AdminEventDetail() {
const { user } = useAuth(); const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
console.log("[ID QRCODE]", id);
console.log("[STATUS Detail]", status);
const [openDrawer, setOpenDrawer] = React.useState(false); const [openDrawer, setOpenDrawer] = React.useState(false);
const newURL = DEEP_LINK_URL
console.log("[DEEP LINK URL]", newURL);
const [data, setData] = React.useState<any | null>(null); const [data, setData] = React.useState<any | null>(null);
const [loadData, setLoadData] = React.useState(false);
const deepLinkURL = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`; const deepLinkURL = `${DEEP_LINK_URL}/--/event/${id}/confirmation?userId=${user?.id}`;
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -48,17 +46,18 @@ export default function AdminEventDetail() {
); );
const onLoadData = async () => { const onLoadData = async () => {
try { try {
setLoadData(true);
const response = await apiAdminEventById({ const response = await apiAdminEventById({
id: id as string, id: id as string,
}); });
// console.log(`[RES DATA BY ID: ${id}]`, JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
}; };
@@ -124,11 +123,19 @@ export default function AdminEventDetail() {
changeStatus: "publish", changeStatus: "publish",
}); });
console.log("[RES PUBLISH]", JSON.stringify(response, null, 2)); if (!response.success) {
Toast.show({
if (response.success) { type: "error",
router.back(); text1: "Gagal mempublikasikan event",
});
return;
} }
Toast.show({
type: "success",
text1: "Event berhasil dipublikasikan",
});
router.back();
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} }
@@ -170,15 +177,19 @@ export default function AdminEventDetail() {
<BaseBox> <BaseBox>
<StackCustom style={{ alignItems: "center" }}> <StackCustom style={{ alignItems: "center" }}>
<TextCustom bold>QR Code Event</TextCustom> <TextCustom bold>QR Code Event</TextCustom>
<QRCode {loadData ? (
value={deepLinkURL} <LoaderCustom />
size={200} ) : (
// logo={require("@/assets/images/logo-hipmi.png")} <QRCode
// logoSize={70} value={deepLinkURL}
// logoBackgroundColor="transparent" size={200}
// logoBorderRadius={50} // logo={require("@/assets/images/logo-hipmi.png")}
// color="black" // logoSize={70}
/> // logoBackgroundColor="transparent"
// logoBorderRadius={50}
// color="black"
/>
)}
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
)} )}

View File

@@ -1,41 +1,81 @@
import { BadgeCustom, BaseBox, Grid, TextCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
BadgeCustom,
BaseBox,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import { MainColor } from "@/constants/color-palet"; import { apiAdminEventListOfParticipants } from "@/service/api-admin/api-admin-event";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function AdminEventListOfParticipants() { export default function AdminEventListOfParticipants() {
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[] | null>(null);
const [loadData, setLoadData] = useState(false);
const isPresent = ({id}: {id: number}) => { useFocusEffect(
const check = id % 3 * 3; useCallback(() => {
if (check === 0) { onLoadData();
return true; }, [id])
} else { );
return false;
} const onLoadData = async () => {
try {
setLoadData(true);
const response = await apiAdminEventListOfParticipants({
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadData(false);
} }
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
headerComponent={<AdminBackButtonAntTitle title="Daftar Peserta" />} headerComponent={<AdminBackButtonAntTitle title="Daftar Peserta" />}
> >
{Array.from({ length: 10 }).map((item, index) => ( {loadData ? (
<BaseBox key={index}> <LoaderCustom />
<Grid> ) : _.isEmpty(listData) ? (
<Grid.Col span={6}> <TextCustom align="center" color="gray">
<TextCustom bold>Username {index + 1}</TextCustom> Belum ada peserta
<TextCustom>+6282123456789</TextCustom> </TextCustom>
</Grid.Col> ) : (
<Grid.Col span={6} style={{ justifyContent: "center" }}> listData?.map((item: any, index: number) => (
<BadgeCustom <BaseBox key={index}>
style={{ alignSelf: "flex-end" }} <Grid>
color={isPresent({id: index}) ? MainColor.green : MainColor.red} <Grid.Col span={6}>
> <StackCustom gap={"sm"}>
{isPresent({id: index}) ? "Hadir" : "Tidak Hadir"} <TextCustom bold truncate>{item?.User?.username}</TextCustom>
</BadgeCustom> <TextCustom>+{item?.User?.nomor}</TextCustom>
</Grid.Col> </StackCustom>
</Grid> </Grid.Col>
</BaseBox> <Grid.Col span={6} style={{ justifyContent: "center" }}>
))} <BadgeCustom
style={{ alignSelf: "flex-end" }}
color={item?.isPresent ? "green" : "red"}
>
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
</BadgeCustom>
</Grid.Col>
</Grid>
</BaseBox>
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -196,6 +196,7 @@ const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
onChange={handleConfirmTime} onChange={handleConfirmTime}
minimumDate={minimumDate} minimumDate={minimumDate}
maximumDate={maximumDate} maximumDate={maximumDate}
themeVariant="light"
/> />
)} )}
</> </>

View File

@@ -145,6 +145,7 @@ const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
onChange={handleConfirm} onChange={handleConfirm}
minimumDate={minimumDate} minimumDate={minimumDate}
maximumDate={maximumDate} maximumDate={maximumDate}
themeVariant="light"
/> />
</View> </View>
</> </>

View File

@@ -48,3 +48,13 @@ export async function apiAdminEventUpdateStatus({
} }
} }
export async function apiAdminEventListOfParticipants({ id }: { id: string }) {
try {
const response = await apiConfig.get(
`/mobile/admin/event/${id}/participants`
);
return response.data;
} catch (error) {
throw error;
}
}