Compare commits

...

2 Commits

Author SHA1 Message Date
4da55a5a8a Integrasi API: Voting admin
Add:
- app/(application)/admin/voting/[id]/[status]/reject-input.tsx
- app/(application)/admin/voting/history.tsx
- components/Box/ReportBox.tsx
- screens/Admin/Voting/
- utils/colorBadge.ts

Fix:
- app/(application)/(user)/job/[id]/[status]/detail.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- app/(application)/admin/job/[id]/[status]/index.tsx
- app/(application)/admin/job/[id]/[status]/reject-input.tsx
- app/(application)/admin/voting/[id]/[status]/index.tsx
- app/(application)/admin/voting/[id]/reject-input.tsx
- app/(application)/admin/voting/[status]/status.tsx
- components/Container/CircleContainer.tsx
- components/Text/TextCustom.tsx
- components/_ShareComponent/Admin/ButtonReview.tsx
- screens/Admin/Job/funUpdateStatus.ts
- screens/Admin/listPageAdmin.tsx
- service/api-admin/api-admin-voting.ts

### No Issue
2025-10-21 16:52:17 +08:00
faf0f36e53 UI Fix: Pada tampilan ios bagian button ada yang ta terlihat dan sudah di perbaiki
### No Issue
2025-10-21 11:17:37 +08:00
24 changed files with 508 additions and 172 deletions

View File

@@ -11,6 +11,7 @@ import {
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import ReportBox from "@/components/Box/ReportBox";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection"; import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
import { apiJobGetOne } from "@/service/api-client/api-job"; import { apiJobGetOne } from "@/service/api-client/api-job";
@@ -70,7 +71,13 @@ export default function JobDetailStatus() {
<LoaderCustom /> <LoaderCustom />
) : ( ) : (
<> <>
<StackCustom> <StackCustom gap={"xs"}>
{data &&
data?.catatan &&
(status === "draft" || status === "rejected") && (
<ReportBox text={data?.catatan} />
)}
<Job_BoxDetailSection data={data} /> <Job_BoxDetailSection data={data} />
<Job_ButtonStatusSection <Job_ButtonStatusSection
id={id as string} id={id as string}

View File

@@ -8,11 +8,13 @@ import {
LoaderCustom, LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon"; import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import ReportBox from "@/components/Box/ReportBox";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection"; import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection"; import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection";
import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection"; import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection";
@@ -49,6 +51,8 @@ export default function VotingDetailStatus() {
setLoadingGetData(true); setLoadingGetData(true);
const response = await apiVotingGetOne({ id: id as string }); const response = await apiVotingGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
} }
@@ -127,6 +131,13 @@ export default function VotingDetailStatus() {
</BaseBox> </BaseBox>
)} )}
<Spacing height={0} /> <Spacing height={0} />
{data &&
data?.catatan &&
(status === "draft" || status === "rejected") && (
<ReportBox text={data?.catatan} />
)}
<Voting_BoxDetailSection data={data as any} /> <Voting_BoxDetailSection data={data as any} />
{status === "publish" ? ( {status === "publish" ? (
<Voting_BoxDetailHasilVotingSection <Voting_BoxDetailHasilVotingSection

View File

@@ -14,7 +14,7 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview"; import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import funUpdateStatus from "@/screens/Admin/Job/funUpdateStatus"; import funUpdateStatusJob from "@/screens/Admin/Job/funUpdateStatus";
import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job"; import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
@@ -88,7 +88,7 @@ export default function AdminJobDetailStatus() {
changeStatus: "publish" | "review" | "reject"; changeStatus: "publish" | "review" | "reject";
}) => { }) => {
try { try {
const response = await funUpdateStatus({ const response = await funUpdateStatusJob({
id: id as string, id: id as string,
changeStatus, changeStatus,
}); });

View File

@@ -7,7 +7,7 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import funUpdateStatus from "@/screens/Admin/Job/funUpdateStatus"; import funUpdateStatusJob from "@/screens/Admin/Job/funUpdateStatus";
import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job"; import { apiAdminJobGetById } from "@/service/api-admin/api-admin-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@@ -45,7 +45,7 @@ export default function AdminJobRejectInput() {
}) => { }) => {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await funUpdateStatus({ const response = await funUpdateStatusJob({
id: id as string, id: id as string,
changeStatus, changeStatus,
data: data, data: data,

View File

@@ -14,8 +14,11 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview"; import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8"; import { GridDetail_4_8 } from "@/components/_ShareComponent/GridDetail_4_8";
import ReportBox from "@/components/Box/ReportBox";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting"; import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { colorBadge } from "@/utils/colorBadge";
import { dateTimeView } from "@/utils/dateTimeView"; import { dateTimeView } from "@/utils/dateTimeView";
import { Entypo } from "@expo/vector-icons"; import { Entypo } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
@@ -23,11 +26,14 @@ import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { List } from "react-native-paper"; import { List } from "react-native-paper";
import Toast from "react-native-toast-message";
export default function AdminVotingDetail() { export default function AdminVotingDetail() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null); const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
console.log("[status]", status);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -41,8 +47,6 @@ export default function AdminVotingDetail() {
id: id as string, id: id as string,
}); });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
} }
@@ -51,18 +55,6 @@ export default function AdminVotingDetail() {
} }
}; };
const colorBadge = () => {
if (status === "publish") {
return MainColor.green;
} else if (status === "review") {
return MainColor.orange;
} else if (status === "reject") {
return MainColor.red;
} else {
return MainColor.placeholder;
}
};
const listData = [ const listData = [
{ {
label: "Username", label: "Username",
@@ -76,8 +68,8 @@ export default function AdminVotingDetail() {
label: "Status", label: "Status",
value: value:
data && data?.Voting_Status?.name ? ( data && data?.Voting_Status?.name ? (
<BadgeCustom color={colorBadge()}> <BadgeCustom color={colorBadge({ status: status as string })}>
{_.startCase(data?.Voting_Status?.name)} {status === "history" ? "Riwayat" : _.startCase(status as string)}
</BadgeCustom> </BadgeCustom>
) : ( ) : (
"-" "-"
@@ -116,6 +108,59 @@ export default function AdminVotingDetail() {
}, },
]; ];
const handleUpdate = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
const dateNow = new Date();
// const dateNowHour = dateNow.getHours();
// const awalVoteHour = dayjs(data?.awalVote).hour();
const isBefore = dayjs(dateNow).diff(dayjs(data?.awalVote), "hours") < 0;
console.log("[IS BEFORE]", isBefore);
if (!isBefore) {
Toast.show({
type: "error",
text1: "Tanggal & waktu telah lewat",
text2: "Silahkan report dan ubah tanggal & waktu voting",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mempublikasikan data",
});
setIsLoading(true);
const response = await funUpdateStatusVoting({
id: id as string,
changeStatus,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mempublikasikan data",
});
}
Toast.show({
type: "success",
text1: "Berhasil mempublikasikan data",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -133,52 +178,59 @@ export default function AdminVotingDetail() {
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
{status === "publish" && ( {status === "publish" ||
<BaseBox> (status === "history" && (
<TextCustom bold align="center"> <BaseBox>
Hasil Voting <TextCustom bold align="center">
</TextCustom> Hasil Voting
<Spacing /> </TextCustom>
<Grid> <Spacing />
{Array.from({ length: 4 }).map((_, index) => ( <Grid>
<Grid.Col {data?.Voting_DaftarNamaVote?.map(
key={index} (item: any, index: number) => (
span={3} <Grid.Col
style={{ paddingRight: 3, paddingLeft: 3 }} key={index}
> span={12 / data?.Voting_DaftarNamaVote?.length}
<StackCustom gap={"sm"}> style={{ paddingRight: 3, paddingLeft: 3 }}
<CircleContainer >
value={(index % 3) * 3} <StackCustom gap={"sm"}>
style={{ alignSelf: "center" }} <CircleContainer
/> value={item?.jumlah}
<TextCustom size="small" align="center"> style={{ alignSelf: "center" }}
Pilihan {index + 1} />
</TextCustom> <TextCustom size="small" align="center">
</StackCustom> {item?.value}
</Grid.Col> </TextCustom>
))} </StackCustom>
</Grid> </Grid.Col>
</BaseBox> )
)} )}
</Grid>
</BaseBox>
))}
{data &&
data?.catatan &&
(status === "review" || status === "reject") && (
<ReportBox text={data?.catatan} />
)}
{status === "review" && ( {status === "review" && (
<AdminButtonReview <AdminButtonReview
isLoading={isLoading}
onPublish={() => { onPublish={() => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Publish", title: "Publish",
message: "Apakah anda yakin ingin mempublikasikan data ini?", message: "Apakah anda yakin ingin mempublikasikan data ini?",
textLeft: "Cancel", textLeft: "Cancel",
textRight: "Publish", textRight: "Publish",
onPressLeft: () => {
router.back();
},
onPressRight: () => { onPressRight: () => {
router.back(); handleUpdate({ changeStatus: "publish" });
}, },
}); });
}} }}
onReject={() => { onReject={() => {
router.push(`/admin/voting/${id}/reject-input`); router.push(`/admin/voting/${id}/${status}/reject-input`);
}} }}
/> />
)} )}
@@ -187,7 +239,7 @@ export default function AdminVotingDetail() {
<AdminButtonReject <AdminButtonReject
title="Tambah Catatan" title="Tambah Catatan"
onReject={() => { onReject={() => {
router.push(`/admin/voting/${id}/reject-input`); router.push(`/admin/voting/${id}/${status}/reject-input`);
}} }}
/> />
)} )}

View File

@@ -0,0 +1,113 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BoxButtonOnFooter,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import funUpdateStatusVoting from "@/screens/Admin/Voting/funUpdateStatus";
import { apiAdminVotingById } from "@/service/api-admin/api-admin-voting";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function AdminVotingRejectInput() {
const { id, status } = useLocalSearchParams();
const [data, setData] = useState("");
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiAdminVotingById({
id: id as string,
});
if (response.success) {
setData(response.data.catatan);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleUpdate = async ({
changeStatus,
}: {
changeStatus: "publish" | "review" | "reject";
}) => {
try {
setIsLoading(true);
const response = await funUpdateStatusVoting({
id: id as string,
changeStatus,
data: data,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Report gagal",
});
}
Toast.show({
type: "success",
text1: "Report berhasil",
});
if (status === "review") {
router.replace(`/admin/voting/reject/status`);
} else if (status === "reject") {
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = (
<BoxButtonOnFooter>
<AdminButtonReject
title="Reject"
isLoading={isLoading}
onReject={() =>
AlertDefaultSystem({
title: "Reject",
message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => handleUpdate({ changeStatus: "reject" }),
})
}
/>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Voting" />}
>
<TextAreaCustom
value={data}
onChangeText={setData}
placeholder="Masukan alasan"
required
showCount
maxLength={1000}
/>
</ViewWrapper>
</>
);
}

View File

@@ -1,55 +0,0 @@
import {
AlertDefaultSystem,
BoxButtonOnFooter,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function AdminVotingRejectInput() {
const { id } = useLocalSearchParams();
const [value, setValue] = useState(id as string);
const buttonSubmit = (
<BoxButtonOnFooter>
<AdminButtonReject
title="Reject"
onReject={() =>
AlertDefaultSystem({
title: "Reject",
message: "Apakah anda yakin ingin menolak data ini?",
textLeft: "Batal",
textRight: "Ya",
onPressLeft: () => {
router.back();
},
onPressRight: () => {
console.log("value:", value);
router.replace(`/admin/voting/reject/status`);
},
})
}
/>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper
footerComponent={buttonSubmit}
headerComponent={<AdminBackButtonAntTitle title="Penolakan Voting" />}
>
<TextAreaCustom
value={value}
onChangeText={setValue}
placeholder="Masukan alasan"
required
showCount
maxLength={1000}
/>
</ViewWrapper>
</>
);
}

View File

@@ -1,10 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon, ActionIcon,
BaseBox,
LoaderCustom, LoaderCustom,
SearchInput, SearchInput,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
@@ -18,13 +16,11 @@ import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
import { Octicons } from "@expo/vector-icons"; import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useState, useCallback } from "react"; import { useCallback, useState } from "react";
import { Divider } from "react-native-paper"; import { Divider } from "react-native-paper";
export default function AdminVotingStatus() { export default function AdminVotingStatus() {
const { status } = useLocalSearchParams(); const { status } = useLocalSearchParams();
console.log("[STATUS]", status);
const [list, setList] = useState<any | null>(null); const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false); const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState<string>(""); const [search, setSearch] = useState<string>("");
@@ -43,8 +39,6 @@ export default function AdminVotingStatus() {
search, search,
}); });
// console.log("[LIST BY STATUS]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setList(response.data); setList(response.data);
} }
@@ -79,31 +73,43 @@ export default function AdminVotingStatus() {
/> />
<Divider /> <Divider />
{loadList ? <LoaderCustom/> : _.isEmpty(list) ? <TextCustom align="center" bold color="gray">Belum ada data</TextCustom> : list.map((item: any, i: number) => ( {loadList ? (
<AdminTableValue <LoaderCustom />
key={i} ) : _.isEmpty(list) ? (
value1={ <TextCustom align="center" bold color="gray">
<ActionIcon Belum ada data
icon={ </TextCustom>
<Octicons ) : (
name="eye" list.map((item: any, i: number) => (
size={ICON_SIZE_BUTTON} <AdminTableValue
color="black" key={i}
/> value1={
} <ActionIcon
onPress={() => { icon={
router.push(`/admin/voting/${item.id}/${status}`); <Octicons
}} name="eye"
/> size={ICON_SIZE_BUTTON}
} color="black"
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>} />
value3={ }
<TextCustom align="center" truncate={2}> onPress={() => {
{item?.title || "-"} router.push(`/admin/voting/${item.id}/${status}`);
</TextCustom> }}
} />
/> }
))} value2={
<TextCustom truncate={1}>
{item?.Author?.username || "-"}
</TextCustom>
}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
</> </>

View File

@@ -0,0 +1,104 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ActionIcon,
LoaderCustom,
SearchInput,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
import { Octicons } from "@expo/vector-icons";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { Divider } from "react-native-paper";
export default function AdminVotingHistory() {
const [list, setList] = useState<any | null>(null);
const [loadList, setLoadList] = useState(false);
const [search, setSearch] = useState<string>("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [ search])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiAdminVoting({
category: "history",
search,
});
if (response.success) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const rightComponent = (
<SearchInput
containerStyle={{ width: "100%", marginBottom: 0 }}
placeholder="Cari"
value={search}
onChangeText={setSearch}
/>
);
return (
<>
<ViewWrapper headerComponent={<AdminTitlePage title="Voting" />}>
<AdminComp_BoxTitle
title="Riwayat"
rightComponent={rightComponent}
/>
<StackCustom gap={"sm"}>
<AdminTitleTable
title1="Aksi"
title2="Username"
title3="Judul Voting"
/>
<Divider />
{loadList ? <LoaderCustom/> : _.isEmpty(list) ? <TextCustom align="center" bold color="gray">Belum ada data</TextCustom> : list.map((item: any, i: number) => (
<AdminTableValue
key={i}
value1={
<ActionIcon
icon={
<Octicons
name="eye"
size={ICON_SIZE_BUTTON}
color="black"
/>
}
onPress={() => {
router.push(`/admin/voting/${item.id}/history`);
}}
/>
}
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
value3={
<TextCustom align="center" truncate={2}>
{item?.title || "-"}
</TextCustom>
}
/>
))}
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -17,7 +17,6 @@
"axios": "^1.11.0", "axios": "^1.11.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"expo": "^54.0.0", "expo": "^54.0.0",
"expo-blur": "~15.0.7",
"expo-camera": "~17.0.7", "expo-camera": "~17.0.7",
"expo-clipboard": "~8.0.7", "expo-clipboard": "~8.0.7",
"expo-constants": "~18.0.8", "expo-constants": "~18.0.8",
@@ -1153,8 +1152,6 @@
"expo-asset": ["expo-asset@12.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-jj2U8zw9+7orST2rlQGULYiqPoECOuUyffs2NguGrq84bYbkM041T7TOMXH2raPVJnM9lEAP54ezI6XL+GVYqw=="], "expo-asset": ["expo-asset@12.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.7", "expo-constants": "~18.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-jj2U8zw9+7orST2rlQGULYiqPoECOuUyffs2NguGrq84bYbkM041T7TOMXH2raPVJnM9lEAP54ezI6XL+GVYqw=="],
"expo-blur": ["expo-blur@15.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-SugQQbQd+zRPy8z2G5qDD4NqhcD7srBF7fN7O7yq6q7ZFK59VWvpDxtMoUkmSfdxgqONsrBN/rLdk00USADrMg=="],
"expo-camera": ["expo-camera@17.0.7", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-jdZfijfFjlVAuuIkDheA41YKpigPjqsN0juRvgyr7Lcyz+fvwZ3/RP50/n/hvuozH657wHxPfiyVVFa00z8TcQ=="], "expo-camera": ["expo-camera@17.0.7", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-jdZfijfFjlVAuuIkDheA41YKpigPjqsN0juRvgyr7Lcyz+fvwZ3/RP50/n/hvuozH657wHxPfiyVVFa00z8TcQ=="],
"expo-clipboard": ["expo-clipboard@8.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-zvlfFV+wB2QQrQnHWlo0EKHAkdi2tycLtE+EXFUWTPZYkgu1XcH+aiKfd4ul7Z0SDF+1IuwoiW9AA9eO35aj3Q=="], "expo-clipboard": ["expo-clipboard@8.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-zvlfFV+wB2QQrQnHWlo0EKHAkdi2tycLtE+EXFUWTPZYkgu1XcH+aiKfd4ul7Z0SDF+1IuwoiW9AA9eO35aj3Q=="],

View File

@@ -0,0 +1,16 @@
import StackCustom from "../Stack/StackCustom";
import TextCustom from "../Text/TextCustom";
import BaseBox from "./BaseBox";
export default function ReportBox({ text }: { text: string }) {
return (
<BaseBox>
<StackCustom gap={"sm"}>
<TextCustom bold>
Catatan penolakan
</TextCustom>
<TextCustom>{text}</TextCustom>
</StackCustom>
</BaseBox>
);
}

View File

@@ -1,27 +1,39 @@
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import React from "react"; import React from "react";
import { StyleProp, StyleSheet, TextInput, View, ViewStyle } from "react-native"; import {
StyleProp,
StyleSheet,
View,
ViewStyle
} from "react-native";
import TextCustom from "../Text/TextCustom";
interface CircularInputProps { interface CircularInputProps {
value?: string | number value?: string | number;
onChange?: (value: string | number) => void; onChange?: (value: string | number) => void;
icon?: React.ReactNode; icon?: React.ReactNode;
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>;
} }
const CircularInput: React.FC<CircularInputProps> = ({ value, onChange, icon, style }) => { const CircularInput: React.FC<CircularInputProps> = ({
value,
onChange,
icon,
style,
}) => {
return ( return (
<View style={[styles.circleContainer, style]}> <View style={[styles.circleContainer, style]}>
{icon ? ( {icon ? (
icon icon
) : ( ) : (
<TextInput <TextCustom
value={String(value)} // text={String(value)}
onChangeText={onChange}
style={styles.input} style={styles.input}
keyboardType="numeric" // keyboardType="numeric"
maxLength={2} // Batasan maksimal karakter // maxLength={2} // Batasan maksimal karakter
/> >
{value}
</TextCustom>
)} )}
</View> </View>
); );
@@ -39,7 +51,7 @@ const styles = StyleSheet.create({
}, },
input: { input: {
color: MainColor.yellow, // Warna kuning color: MainColor.yellow, // Warna kuning
fontSize: 24, fontSize: 18,
fontWeight: "bold", fontWeight: "bold",
textAlign: "center", textAlign: "center",
padding: 0, padding: 0,

View File

@@ -23,7 +23,7 @@ interface TextCustomProps {
bold?: boolean; bold?: boolean;
semiBold?: boolean; semiBold?: boolean;
size?: "default" | "large" | "small" | "xlarge" | number size?: "default" | "large" | "small" | "xlarge" | number
color?: "default" | "yellow" | "red" | "gray" | "green" | "black" color?: "default" | "yellow" | "red" | "gray" | "green" | "black" | "orange"
align?: TextAlign; // Prop untuk alignment align?: TextAlign; // Prop untuk alignment
truncate?: boolean | number; truncate?: boolean | number;
onPress?: () => void; onPress?: () => void;
@@ -62,6 +62,7 @@ const TextCustom: React.FC<TextCustomProps> = ({
else if (color === "gray") selectedStyles.push(styles.gray); else if (color === "gray") selectedStyles.push(styles.gray);
else if (color === "green") selectedStyles.push(styles.green); else if (color === "green") selectedStyles.push(styles.green);
else if (color === "black") selectedStyles.push(styles.black); else if (color === "black") selectedStyles.push(styles.black);
else if (color === "orange") selectedStyles.push(styles.orange);
// Alignment // Alignment
if (align) { if (align) {
@@ -140,4 +141,7 @@ export const styles = StyleSheet.create({
black: { black: {
color: MainColor.black, color: MainColor.black,
}, },
orange: {
color: MainColor.orange,
},
}); });

View File

@@ -14,7 +14,7 @@ export default function AdminButtonReject({
return ( return (
<> <>
<ButtonCustom <ButtonCustom
iconLeft={<IconReject />} iconLeft={<IconReject size={16} />}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor="white" textColor="white"
onPress={onReject} onPress={onReject}

View File

@@ -4,9 +4,11 @@ import Grid from "@/components/Grid/GridCustom";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
export default function AdminButtonReview({ export default function AdminButtonReview({
isLoading,
onPublish, onPublish,
onReject, onReject,
}: { }: {
isLoading?: boolean;
onPublish: () => void; onPublish: () => void;
onReject: () => void; onReject: () => void;
}) { }) {
@@ -15,6 +17,7 @@ export default function AdminButtonReview({
<Grid> <Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}> <Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
iconLeft={<IconPublish />} iconLeft={<IconPublish />}
backgroundColor={MainColor.green} backgroundColor={MainColor.green}
textColor="white" textColor="white"

View File

@@ -12,7 +12,7 @@ import {
StyleProp, StyleProp,
ViewStyle, ViewStyle,
} from "react-native"; } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; import { NativeSafeAreaViewProps, SafeAreaView } from "react-native-safe-area-context";
interface ViewWrapperProps { interface ViewWrapperProps {
children: React.ReactNode; children: React.ReactNode;
@@ -21,6 +21,7 @@ interface ViewWrapperProps {
footerComponent?: React.ReactNode; footerComponent?: React.ReactNode;
floatingButton?: React.ReactNode; floatingButton?: React.ReactNode;
hideFooter?: boolean; hideFooter?: boolean;
edgesFooter?: NativeSafeAreaViewProps["edges"];
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
} }
@@ -37,6 +38,7 @@ const ViewWrapper = ({
footerComponent, footerComponent,
floatingButton, floatingButton,
hideFooter = false, hideFooter = false,
edgesFooter =[],
style, style,
}: ViewWrapperProps) => { }: ViewWrapperProps) => {
const assetBackground = require("../../assets/images/main-background.png"); const assetBackground = require("../../assets/images/main-background.png");
@@ -77,7 +79,7 @@ const ViewWrapper = ({
{footerComponent ? ( {footerComponent ? (
<SafeAreaView <SafeAreaView
edges={["bottom"]} edges={Platform.OS === "ios" ? edgesFooter : ["bottom"]}
style={{ style={{
backgroundColor: MainColor.darkblue, backgroundColor: MainColor.darkblue,
height: OS_HEIGHT height: OS_HEIGHT

View File

@@ -23,7 +23,7 @@ export {
// OS Height // OS Height
const OS_ANDROID_HEIGHT = 115 const OS_ANDROID_HEIGHT = 115
const OS_IOS_HEIGHT = 65 const OS_IOS_HEIGHT = 70
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
// Text Size // Text Size

View File

@@ -24,7 +24,6 @@
"axios": "^1.11.0", "axios": "^1.11.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"expo": "^54.0.0", "expo": "^54.0.0",
"expo-blur": "~15.0.7",
"expo-camera": "~17.0.7", "expo-camera": "~17.0.7",
"expo-clipboard": "~8.0.7", "expo-clipboard": "~8.0.7",
"expo-constants": "~18.0.8", "expo-constants": "~18.0.8",

View File

@@ -1,6 +1,6 @@
import { apiAdminJobUpdate } from "@/service/api-admin/api-admin-job"; import { apiAdminJobUpdate } from "@/service/api-admin/api-admin-job";
const funUpdateStatus = async ({ const funUpdateStatusJob = async ({
id, id,
changeStatus, changeStatus,
data, data,
@@ -23,4 +23,4 @@ const funUpdateStatus = async ({
} }
}; };
export default funUpdateStatus; export default funUpdateStatusJob;

View File

@@ -0,0 +1,26 @@
import { apiAdminVotingUpdateStatus } from "@/service/api-admin/api-admin-voting";
const funUpdateStatusVoting = async ({
id,
changeStatus,
data,
}: {
id: string;
changeStatus: "publish" | "review" | "reject";
data?: string;
}) => {
try {
const response = await apiAdminVotingUpdateStatus({
id: id,
status: changeStatus as any,
data: data,
});
return response;
} catch (error) {
console.log("[ERROR]", error);
throw error;
}
};
export default funUpdateStatusVoting;

View File

@@ -49,7 +49,7 @@ const adminListMenu: NavbarItem[] = [
{ label: "Publish", link: "/admin/voting/publish/status" }, { label: "Publish", link: "/admin/voting/publish/status" },
{ label: "Review", link: "/admin/voting/review/status" }, { label: "Review", link: "/admin/voting/review/status" },
{ label: "Reject", link: "/admin/voting/reject/status" }, { label: "Reject", link: "/admin/voting/reject/status" },
{ label: "Riwayat", link: "/admin/voting/riwayat/status" }, { label: "Riwayat", link: "/admin/voting/history" },
], ],
}, },
{ {
@@ -142,7 +142,7 @@ const superAdminListMenu: NavbarItem[] = [
{ label: "Publish", link: "/admin/voting/publish/status" }, { label: "Publish", link: "/admin/voting/publish/status" },
{ label: "Review", link: "/admin/voting/review/status" }, { label: "Review", link: "/admin/voting/review/status" },
{ label: "Reject", link: "/admin/voting/reject/status" }, { label: "Reject", link: "/admin/voting/reject/status" },
{ label: "Riwayat", link: "/admin/voting/riwayat/status" }, { label: "Riwayat", link: "/admin/voting/history" },
], ],
}, },
{ {

View File

@@ -17,11 +17,7 @@ export async function apiAdminVoting({
} }
} }
export async function apiAdminVotingById({ export async function apiAdminVotingById({ id }: { id: string }) {
id,
}: {
id: string;
}) {
try { try {
const response = await apiConfig.get(`/mobile/admin/voting/${id}`); const response = await apiConfig.get(`/mobile/admin/voting/${id}`);
return response.data; return response.data;
@@ -29,3 +25,25 @@ export async function apiAdminVotingById({
throw error; throw error;
} }
} }
export async function apiAdminVotingUpdateStatus({
id,
data,
status,
}: {
id: string;
data?: string;
status: "publish" | "review" | "reject";
}) {
try {
const response = await apiConfig.put(
`/mobile/admin/voting/${id}?status=${status}`,
{
data: data,
}
);
return response.data;
} catch (error) {
throw error;
}
}

View File

@@ -195,7 +195,14 @@ export const GStyles = StyleSheet.create({
bottomBar: { bottomBar: {
backgroundColor: MainColor.darkblue, backgroundColor: MainColor.darkblue,
borderTopColor: AccentColor.blue, borderTopColor: AccentColor.blue,
borderTopWidth: 1, // borderTopWidth: 0.5,
height: "100%",
justifyContent: "center",
shadowColor: AccentColor.blue,
shadowOffset: { width: 0, height: -5},
shadowOpacity: 0.4,
shadowRadius: 40,
elevation: 8, // untuk Android
}, },
bottomBarContainer: { bottomBarContainer: {
paddingHorizontal: 15, paddingHorizontal: 15,

14
utils/colorBadge.ts Normal file
View File

@@ -0,0 +1,14 @@
import { MainColor } from "@/constants/color-palet";
export const colorBadge = ({ status }: { status: string }) => {
const statusLowerCase = status.toLowerCase();
if (statusLowerCase === "publish") {
return MainColor.green;
} else if (statusLowerCase === "review") {
return MainColor.orange;
} else if (statusLowerCase === "reject") {
return MainColor.red;
} else {
return MainColor.placeholder;
}
};