## Perubahan Tampilan Admin
### File Baru (4) - `screens/Admin/Voting/ScreenVotingStatus.tsx` - `screens/Admin/Voting/ScreenVotingHistory.tsx` - `screens/Admin/Voting/ScreenEventTypeOfEvent.tsx` - `screens/Admin/Voting/BoxVotingStatus.tsx` ### File Diubah (3) - `app/(application)/admin/voting/[status]/status.tsx` → 5 baris - `app/(application)/admin/voting/history.tsx` → 5 baris - `app/(application)/admin/event/type-of-event.tsx` → 5 baris ### API Updates (2) - `service/api-admin/api-admin-voting.ts` → tambah param `page` - `service/api-admin/api-master-admin.ts` → tambah param `page` ## Fitur Baru - Pagination (infinite scroll) - Pull-to-Refresh - Skeleton Loading - Empty State - Search Functionality ### No Issue"
This commit is contained in:
54
screens/Admin/Voting/BoxVotingStatus.tsx
Normal file
54
screens/Admin/Voting/BoxVotingStatus.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Divider, StackCustom, TextCustom } from "@/components";
|
||||
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
|
||||
interface BoxVotingStatusProps {
|
||||
item: any;
|
||||
status?: string;
|
||||
path: any;
|
||||
}
|
||||
|
||||
export default function Admin_BoxVotingStatus({
|
||||
item,
|
||||
status,
|
||||
path,
|
||||
}: BoxVotingStatusProps) {
|
||||
return (
|
||||
<>
|
||||
<AdminBasicBox
|
||||
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||
onPress={() => {
|
||||
router.push(path);
|
||||
}}
|
||||
>
|
||||
<StackCustom gap={0}>
|
||||
<View style={{ paddingBlock: 8 }}>
|
||||
<TextCustom size={"large"} bold truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
<Divider />
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Mulai</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
{dateTimeView({ date: item?.awalVote }) || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Berakhir</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
{dateTimeView({ date: item?.akhirVote }) || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</AdminBasicBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
152
screens/Admin/Voting/ScreenEventTypeOfEvent.tsx
Normal file
152
screens/Admin/Voting/ScreenEventTypeOfEvent.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { BadgeCustom, TextCustom } from "@/components";
|
||||
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
|
||||
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiAdminMasterTypeOfEvent } from "@/service/api-admin/api-master-admin";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
export function Admin_ScreenEventTypeOfEvent() {
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminMasterTypeOfEvent({
|
||||
page: String(page),
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return { data: response.data };
|
||||
} else {
|
||||
return { data: [] };
|
||||
}
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
searchQuery: search,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
// Komponen action plus untuk header
|
||||
const rightComponent = useMemo(
|
||||
() => (
|
||||
<AdminActionIconPlus
|
||||
onPress={() => {
|
||||
router.push(`/admin/event/type-create`);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Header component untuk title
|
||||
const headerComponent = useMemo(
|
||||
() => (
|
||||
<AdminComp_BoxTitle title="Tipe Acara" rightComponent={rightComponent} />
|
||||
),
|
||||
[rightComponent],
|
||||
);
|
||||
|
||||
// Render header tabel (Aksi, Status, Tipe Acara)
|
||||
const renderTableHeader = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<GridViewCustomSpan
|
||||
span1={2}
|
||||
span2={5}
|
||||
span3={5}
|
||||
component1={
|
||||
<TextCustom bold align="center">
|
||||
Aksi
|
||||
</TextCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom bold align="center">
|
||||
Status
|
||||
</TextCustom>
|
||||
}
|
||||
component3={<TextCustom bold>Tipe Acara</TextCustom>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Render item untuk daftar tipe event (mengikuti pattern InformationBankSection)
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<AdminBasicBox
|
||||
onPress={() => {
|
||||
router.push(`/admin/event/type-update?id=${item.id}`);
|
||||
}}
|
||||
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||
>
|
||||
<GridTwoView
|
||||
leftItem={<TextCustom bold>{item?.name || "-"}</TextCustom>}
|
||||
rightItem={
|
||||
<View>
|
||||
{item?.active ? (
|
||||
<BadgeCustom color="green">Aktif</BadgeCustom>
|
||||
) : (
|
||||
<BadgeCustom color="red">Tidak Aktif</BadgeCustom>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
spanLeft={8}
|
||||
spanRight={4}
|
||||
styleRight={{
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
/>
|
||||
</AdminBasicBox>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, []),
|
||||
);
|
||||
|
||||
// Buat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: search,
|
||||
emptyMessage: "Belum ada data",
|
||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 100,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item: any) => item.id?.toString() || `fallback-${item.id}`}
|
||||
headerComponent={headerComponent}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor="#E1B525"
|
||||
colors={["#E1B525"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
102
screens/Admin/Voting/ScreenVotingHistory.tsx
Normal file
102
screens/Admin/Voting/ScreenVotingHistory.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { SearchInput } from "@/components";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import {
|
||||
PAGINATION_DEFAULT_TAKE
|
||||
} from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Admin_BoxVotingStatus from "./BoxVotingStatus";
|
||||
|
||||
export function Admin_ScreenVotingHistory() {
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminVoting({
|
||||
category: "history",
|
||||
search: searchQuery,
|
||||
page: String(page),
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return { data: response.data };
|
||||
} else {
|
||||
return { data: [] };
|
||||
}
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
searchQuery: search,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
// Komponen search input untuk header
|
||||
const rightComponent = useMemo(
|
||||
() => (
|
||||
<SearchInput
|
||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||
placeholder="Cari"
|
||||
value={search}
|
||||
onChangeText={(value) => setSearch(value)}
|
||||
/>
|
||||
),
|
||||
[search],
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<Admin_BoxVotingStatus
|
||||
key={index}
|
||||
item={item}
|
||||
path={`/admin/voting/${item.id}/history`}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Header component dengan judul voting history
|
||||
const headerComponent = useMemo(
|
||||
() => (
|
||||
<AdminComp_BoxTitle title="Riwayat" rightComponent={rightComponent} />
|
||||
),
|
||||
[rightComponent],
|
||||
);
|
||||
|
||||
// Buat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: search,
|
||||
emptyMessage: "Belum ada data",
|
||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 80,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item: any) => item.id?.toString() || `fallback-${item.id}`}
|
||||
headerComponent={headerComponent}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor="#E1B525"
|
||||
colors={["#E1B525"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
110
screens/Admin/Voting/ScreenVotingStatus.tsx
Normal file
110
screens/Admin/Voting/ScreenVotingStatus.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { SearchInput } from "@/components";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import {
|
||||
PAGINATION_DEFAULT_TAKE
|
||||
} from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiAdminVoting } from "@/service/api-admin/api-admin-voting";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Admin_BoxVotingStatus from "./BoxVotingStatus";
|
||||
|
||||
export function Admin_ScreenVotingStatus() {
|
||||
const { status } = useLocalSearchParams();
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminVoting({
|
||||
category: status as any,
|
||||
search: searchQuery,
|
||||
page: String(page),
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return { data: response.data };
|
||||
} else {
|
||||
return { data: [] };
|
||||
}
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
searchQuery: search,
|
||||
dependencies: [status],
|
||||
});
|
||||
|
||||
// Komponen search input untuk header
|
||||
const rightComponent = useMemo(
|
||||
() => (
|
||||
<SearchInput
|
||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||
placeholder="Cari"
|
||||
value={search}
|
||||
onChangeText={(value) => setSearch(value)}
|
||||
/>
|
||||
),
|
||||
[search],
|
||||
);
|
||||
|
||||
// Render item untuk daftar voting
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<Admin_BoxVotingStatus
|
||||
key={index}
|
||||
item={item}
|
||||
status={status as string}
|
||||
path={`/admin/voting/${item.id}/${status}`}
|
||||
/>
|
||||
),
|
||||
[status],
|
||||
);
|
||||
|
||||
// Header component dengan judul status voting
|
||||
const headerComponent = useMemo(
|
||||
() => (
|
||||
<AdminComp_BoxTitle
|
||||
title={`Voting ${_.startCase(status as string)}`}
|
||||
rightComponent={rightComponent}
|
||||
/>
|
||||
),
|
||||
[status, rightComponent],
|
||||
);
|
||||
|
||||
// Buat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: search,
|
||||
emptyMessage: "Belum ada data",
|
||||
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||
isInitialLoad: pagination.isInitialLoad,
|
||||
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||
skeletonHeight: 80,
|
||||
});
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
listData={pagination.listData}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item: any) => item.id?.toString() || `fallback-${item.id}`}
|
||||
headerComponent={headerComponent}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onEndReached={pagination.loadMore}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={pagination.refreshing}
|
||||
onRefresh={pagination.onRefresh}
|
||||
tintColor="#E1B525"
|
||||
colors={["#E1B525"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
ViewWrapper,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
Grid,
|
||||
ButtonCustom,
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
@@ -15,8 +15,8 @@ import { apiMapsGetAll } from "@/service/api-client/api-maps";
|
||||
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
|
||||
import { FontAwesome, Ionicons } from "@expo/vector-icons";
|
||||
import { Image } from "expo-image";
|
||||
import { useFocusEffect, router } from "expo-router";
|
||||
import { useState, useCallback } from "react";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import MapView, { Marker } from "react-native-maps";
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function MapsView() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadList();
|
||||
}, [])
|
||||
}, []),
|
||||
);
|
||||
|
||||
const handlerLoadList = async () => {
|
||||
@@ -146,52 +146,52 @@ export default function MapsView() {
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<FontAwesome
|
||||
name="building-o"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.namePin}</TextCustom>}
|
||||
rightItem={<TextCustom>{selected.namePin}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<Ionicons
|
||||
name="list-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.bidangBisnis}</TextCustom>}
|
||||
rightItem={<TextCustom>{selected.bidangBisnis}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<Ionicons
|
||||
name="call-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.nomorTelepon}</TextCustom>}
|
||||
rightItem={<TextCustom>{selected.nomorTelepon}</TextCustom>}
|
||||
/>
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{selected.alamatBisnis}</TextCustom>}
|
||||
rightItem={<TextCustom>{selected.alamatBisnis}</TextCustom>}
|
||||
/>
|
||||
|
||||
<Grid>
|
||||
|
||||
Reference in New Issue
Block a user