Merge pull request 'fixed-admin/18-feb-26' (#55) from fixed-admin/18-feb-26 into staging
Reviewed-on: #55
This commit is contained in:
169
QWEN.md
169
QWEN.md
@@ -35,6 +35,7 @@ hipmi-mobile/
|
||||
│ └── ...
|
||||
├── assets/ # Images, icons, and static assets
|
||||
├── constants/ # Constants and configuration values
|
||||
├── helpers/ # Helper functions (pagination, etc.)
|
||||
├── hooks/ # Custom React hooks
|
||||
├── lib/ # Utility libraries
|
||||
├── navigation/ # Navigation configuration
|
||||
@@ -49,6 +50,8 @@ hipmi-mobile/
|
||||
- Node.js (with bun as the package manager)
|
||||
- Expo CLI
|
||||
- iOS Simulator or Android Emulator (for native builds)
|
||||
- Android Studio (for Android builds)
|
||||
- Xcode (for iOS builds, macOS only)
|
||||
|
||||
### Setup and Development
|
||||
|
||||
@@ -76,28 +79,79 @@ hipmi-mobile/
|
||||
bun run lint
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
The application uses environment variables defined in the app.config.js file:
|
||||
### Build Commands
|
||||
|
||||
#### EAS Build (Production)
|
||||
```bash
|
||||
# Production build
|
||||
eas build --profile production
|
||||
|
||||
# Preview build
|
||||
eas build --profile preview
|
||||
|
||||
# Development build
|
||||
eas build --profile development
|
||||
```
|
||||
|
||||
#### Local Native Builds
|
||||
```bash
|
||||
# Generate native folders (iOS & Android)
|
||||
npx expo prebuild
|
||||
|
||||
# iOS specific
|
||||
bunx expo prebuild --platform ios
|
||||
open ios/HIPMIBADUNG.xcworkspace
|
||||
|
||||
# Android specific
|
||||
bunx expo prebuild --platform android
|
||||
```
|
||||
|
||||
#### Version Management
|
||||
```bash
|
||||
# Patch version update
|
||||
npm version patch
|
||||
```
|
||||
|
||||
### Android Debugging
|
||||
```bash
|
||||
# List connected devices
|
||||
adb devices
|
||||
|
||||
# Install APK to device/emulator
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Install to specific device
|
||||
adb -s <device_id> install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The application uses environment variables defined in the `app.config.js` file:
|
||||
- `API_BASE_URL`: Base URL for API endpoints
|
||||
- `BASE_URL`: Base application URL
|
||||
- `DEEP_LINK_URL`: URL for deep linking functionality
|
||||
|
||||
### EAS Build Configuration
|
||||
Create a `.env` file in the project root with these variables.
|
||||
|
||||
## EAS Build Configuration
|
||||
|
||||
The project uses Expo Application Services (EAS) for building and deploying:
|
||||
- Development builds with development client
|
||||
- Preview builds for internal distribution
|
||||
- Production builds for app stores
|
||||
- **Development**: Development builds with development client
|
||||
- **Preview**: Internal distribution builds (APK for Android)
|
||||
- **Production**: App store builds (App Bundle for Android, IPA for iOS)
|
||||
|
||||
Configuration is in `eas.json`.
|
||||
|
||||
## Features and Functionality
|
||||
|
||||
The application appears to include several key modules:
|
||||
- **Authentication**: Login, registration, and verification flows
|
||||
- **Admin Panel**: Administrative functions
|
||||
The application includes several key modules:
|
||||
- **Authentication**: Login with phone number, OTP verification, registration, terms acceptance
|
||||
- **Admin Panel**: Administrative functions for managing content and users
|
||||
- **Collaboration**: Tools for member collaboration
|
||||
- **Events**: Event management and calendar
|
||||
- **Forum**: Discussion forums
|
||||
- **Maps**: Location-based services with Mapbox integration
|
||||
- **Donations**: Donation functionality
|
||||
- **Donations**: Donation functionality with fund disbursement tracking
|
||||
- **Job Board**: Employment opportunities
|
||||
- **Investment**: Investment-related features
|
||||
- **Voting**: Voting systems
|
||||
@@ -109,18 +163,50 @@ The application appears to include several key modules:
|
||||
### Coding Standards
|
||||
- TypeScript is used throughout the project for type safety
|
||||
- Component-based architecture with reusable components
|
||||
- Context API for state management
|
||||
- Context API for state management (AuthContext)
|
||||
- File-based routing with Expo Router
|
||||
- Consistent naming conventions using camelCase for variables and PascalCase for components
|
||||
- Path aliases: `@/*` maps to project root
|
||||
|
||||
### Architecture Patterns
|
||||
|
||||
#### Screen Components
|
||||
- Screen components are stored in `/screens` directory organized by feature
|
||||
- Route files in `/app` import and use screen components
|
||||
- Example pattern:
|
||||
```tsx
|
||||
// app/some-route.tsx
|
||||
import SomeScreen from "@/screens/Feature/ScreenSome";
|
||||
|
||||
export default function SomeRoute() {
|
||||
return <SomeScreen />;
|
||||
}
|
||||
```
|
||||
|
||||
#### Wrapper Components
|
||||
- `NewWrapper` component is used for consistent screen layouts
|
||||
- Located at `components/_ShareComponent/NewWrapper.tsx`
|
||||
|
||||
#### Pagination Pattern
|
||||
- Use `hooks/use-pagination.tsx` and `helpers/paginationHelpers.tsx`
|
||||
- Helper functions: `createSkeletonList`, `createEmptyState`, `createLoadingFooter`, `createPaginationComponents`
|
||||
- API functions should accept `page` parameter (default: "1")
|
||||
|
||||
### API Service Structure
|
||||
- Base API configuration: `service/api-config.ts`
|
||||
- Client APIs: `service/api-client/`
|
||||
- Admin APIs: `service/api-admin/`
|
||||
- All API calls use axios with interceptors for auth token injection
|
||||
|
||||
### Testing
|
||||
- Linting is configured with ESLint
|
||||
- Standard Expo linting configuration is used
|
||||
- Standard Expo linting configuration
|
||||
|
||||
### Security
|
||||
- Firebase is integrated for authentication and messaging
|
||||
- Camera and location permissions are properly configured
|
||||
- Deep linking is secured with app domain associations
|
||||
- Auth tokens stored in AsyncStorage
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
@@ -133,6 +219,9 @@ The application appears to include several key modules:
|
||||
- `react-native-toast-message`: Toast notifications
|
||||
- `react-native-otp-entry`: OTP input components
|
||||
- `react-native-qrcode-svg`: QR code generation
|
||||
- `axios`: HTTP client for API calls
|
||||
- `lodash`: Utility library
|
||||
- `moti`: Animation library
|
||||
|
||||
### Development Dependencies
|
||||
- `@types/*`: TypeScript type definitions
|
||||
@@ -142,28 +231,52 @@ The application appears to include several key modules:
|
||||
## Platform Support
|
||||
|
||||
The application is configured to support:
|
||||
- **iOS**: With tablet support and proper permissions
|
||||
- **Android**: With adaptive icons and intent filters for deep linking
|
||||
- **iOS**:
|
||||
- Bundle identifier: `com.anonymous.hipmi-mobile`
|
||||
- Supports tablets
|
||||
- Build number: 21
|
||||
- Google Services integration
|
||||
- Associated domains for deep linking
|
||||
- **Android**:
|
||||
- Package name: `com.bip.hipmimobileapp`
|
||||
- Version code: 4
|
||||
- Adaptive icons
|
||||
- Edge-to-edge display enabled
|
||||
- Intent filters for HTTPS deep linking
|
||||
- **Web**: Static output configuration for web deployment
|
||||
|
||||
## Special Configurations
|
||||
|
||||
### iOS Configuration
|
||||
- Bundle identifier: `com.anonymous.hipmi-mobile`
|
||||
- Supports tablets
|
||||
- Google Services integration
|
||||
- Location permission handling
|
||||
- Associated domains for deep linking
|
||||
|
||||
### Android Configuration
|
||||
- Package name: `com.bip.hipmimobileapp`
|
||||
- Adaptive icons
|
||||
- Edge-to-edge display enabled
|
||||
- Intent filters for HTTPS deep linking
|
||||
- Google Services integration
|
||||
### Deep Linking
|
||||
- Scheme: `hipmimobile://`
|
||||
- Associated domains: `applinks:cld-dkr-staging-hipmi.wibudev.com`
|
||||
- Configured for both iOS and Android
|
||||
|
||||
### Maps Integration
|
||||
The application uses Mapbox for mapping functionality with the `@rnmapbox/maps` plugin.
|
||||
|
||||
### Push Notifications
|
||||
Firebase Cloud Messaging is integrated for push notifications with proper configuration for both iOS and Android platforms.
|
||||
Firebase Cloud Messaging is integrated for push notifications with proper configuration for both iOS and Android platforms.
|
||||
|
||||
### Camera
|
||||
Camera permissions configured for both iOS and Android with microphone access for recording.
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding a New Screen
|
||||
1. Create screen component in appropriate `/screens` subdirectory
|
||||
2. Add route in `/app` directory if needed
|
||||
3. Configure navigation in `AppRoot.tsx` if custom header is needed
|
||||
|
||||
### Adding API Endpoint
|
||||
1. Add function in appropriate service file (`service/api-client/` or `service/api-admin/`)
|
||||
2. Use `apiConfig` axios instance for requests
|
||||
3. Include proper error handling
|
||||
|
||||
### Refactoring Pattern (from docs/prompt-for-qwen-code.md)
|
||||
When moving code from route files to screen components:
|
||||
1. Create new file in `screens/<Feature>/` directory
|
||||
2. Rename function with prefix (e.g., `Admin_`, `Donation_`)
|
||||
3. Use `NewWrapper` component for consistent layout
|
||||
4. Apply pagination helpers if displaying lists
|
||||
5. Import and call from original route file
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
"Aplikasi membutuhkan akses lokasi untuk menampilkan peta.",
|
||||
},
|
||||
associatedDomains: ["applinks:cld-dkr-staging-hipmi.wibudev.com"],
|
||||
buildNumber: "20",
|
||||
buildNumber: "21",
|
||||
},
|
||||
|
||||
android: {
|
||||
|
||||
@@ -77,14 +77,14 @@ export default function Application() {
|
||||
);
|
||||
}
|
||||
|
||||
if (data && data?.masterUserRoleId !== "1") {
|
||||
console.log("User is not admin");
|
||||
return (
|
||||
<BasicWrapper>
|
||||
<Redirect href={`/admin/dashboard`} />
|
||||
</BasicWrapper>
|
||||
);
|
||||
}
|
||||
// if (data && data?.masterUserRoleId !== "1") {
|
||||
// console.log("User is not admin");
|
||||
// return (
|
||||
// <BasicWrapper>
|
||||
// <Redirect href={`/admin/dashboard`} />
|
||||
// </BasicWrapper>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
@@ -49,7 +49,7 @@ export default function Portofolio() {
|
||||
useCallback(() => {
|
||||
onLoadData(id as string);
|
||||
onLoadUserByToken();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
async function onLoadData(id: string) {
|
||||
@@ -144,27 +144,27 @@ export default function Portofolio() {
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<FontAwesome
|
||||
name="building-o"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{data?.BusinessMaps?.namePin}</TextCustom>}
|
||||
rightItem={<TextCustom>{data?.BusinessMaps?.namePin}</TextCustom>}
|
||||
/>
|
||||
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<Ionicons
|
||||
name="list-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={
|
||||
rightItem={
|
||||
<TextCustom>{data?.MasterBidangBisnis?.name}</TextCustom>
|
||||
}
|
||||
/>
|
||||
@@ -172,26 +172,26 @@ export default function Portofolio() {
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<Ionicons
|
||||
name="call-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{data?.tlpn}</TextCustom>}
|
||||
rightItem={<TextCustom>{data?.tlpn}</TextCustom>}
|
||||
/>
|
||||
<GridTwoView
|
||||
spanLeft={2}
|
||||
spanRight={10}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={ICON_SIZE_SMALL}
|
||||
color="white"
|
||||
/>
|
||||
}
|
||||
rightIcon={<TextCustom>{data?.alamatKantor}</TextCustom>}
|
||||
rightItem={<TextCustom>{data?.alamatKantor}</TextCustom>}
|
||||
/>
|
||||
|
||||
<Spacing />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
BaseBox,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
Grid,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
@@ -22,7 +22,7 @@ export default function AdminCollaborationPublish() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadData();
|
||||
}, [status])
|
||||
}, [status]),
|
||||
);
|
||||
|
||||
const handlerLoadData = async () => {
|
||||
@@ -78,16 +78,16 @@ export default function AdminCollaborationPublish() {
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
{data?.report && (
|
||||
<BaseBox>
|
||||
<GridTwoView
|
||||
spanLeft={4}
|
||||
spanRight={8}
|
||||
leftIcon={<TextCustom bold>Catatan report</TextCustom>}
|
||||
rightIcon={<TextCustom>{data?.report}</TextCustom>}
|
||||
/>
|
||||
</BaseBox>
|
||||
)}
|
||||
{data?.report && (
|
||||
<BaseBox>
|
||||
<GridTwoView
|
||||
spanLeft={4}
|
||||
spanRight={8}
|
||||
leftItem={<TextCustom bold>Catatan report</TextCustom>}
|
||||
rightItem={<TextCustom>{data?.report}</TextCustom>}
|
||||
/>
|
||||
</BaseBox>
|
||||
)}
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -42,19 +42,19 @@ export default function AdminDonationDetailDisbursementOfFunds() {
|
||||
const listData = [
|
||||
{
|
||||
label: "Nominal",
|
||||
value: `Rp ${(data && formatCurrencyDisplay(data?.nominalCair)) || 0}`,
|
||||
value: `Rp ${data ? formatCurrencyDisplay(data?.nominalCair) : 0}`,
|
||||
},
|
||||
{
|
||||
label: "Tanggal",
|
||||
value: dateTimeView({ date: data?.createdAt }),
|
||||
value: data ? dateTimeView({ date: data?.createdAt }) : "-",
|
||||
},
|
||||
{
|
||||
label: "Judul",
|
||||
value: (data && data?.title) || "-",
|
||||
value: data ? data?.title : "-",
|
||||
},
|
||||
{
|
||||
label: "Deskripsi",
|
||||
value: (data && data?.deskripsi) || "-",
|
||||
value: data ? data?.deskripsi : "-",
|
||||
},
|
||||
];
|
||||
return (
|
||||
|
||||
@@ -1,126 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
CenterCustom,
|
||||
Divider,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconView } from "@/components/_Icon/IconComponent";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-donation";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import dayjs from "dayjs";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useCallback } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Admin_ScreenDonationListDisbursementOfFunds } from "@/screens/Admin/Donation/ScreenDonationListDisbursementOfFunds";
|
||||
|
||||
export default function AdminDonasiListOfDisbursementOfFunds() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = React.useState<any[] | null>(null);
|
||||
const [loadData, setLoadData] = React.useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
const response = await apiAdminDonationDisbursementOfFundsListById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
headerComponent={
|
||||
<AdminBackButtonAntTitle title="Daftar Pencairan Dana" />
|
||||
}
|
||||
>
|
||||
<GridViewCustomSpan
|
||||
span1={3}
|
||||
span2={5}
|
||||
span3={4}
|
||||
component1={
|
||||
<TextCustom bold align="center">
|
||||
Aksi
|
||||
</TextCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom bold align="center">
|
||||
Tanggal
|
||||
</TextCustom>
|
||||
}
|
||||
component3={
|
||||
<TextCustom bold align="center">
|
||||
Nominal
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<StackCustom>
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item, index) => (
|
||||
<View key={index}>
|
||||
<GridViewCustomSpan
|
||||
span1={3}
|
||||
span2={5}
|
||||
span3={4}
|
||||
component1={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconView size={ICON_SIZE_BUTTON} color="black" />
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/donation/${item?.id}/detail-disbursement-of-funds`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom align="center" truncate>
|
||||
{dayjs(item?.createdAt).format("DD-MM-YYYY")}
|
||||
</TextCustom>
|
||||
}
|
||||
component3={
|
||||
<TextCustom align="center" truncate>
|
||||
Rp. {formatCurrencyDisplay(item?.nominalCair)}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenDonationListDisbursementOfFunds />;
|
||||
}
|
||||
|
||||
@@ -1,186 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
ActionIcon,
|
||||
BadgeCustom,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
SelectCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { IconView } from "@/components/_Icon/IconComponent";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminDonationListOfDonatur } from "@/service/api-admin/api-admin-donation";
|
||||
import { apiMasterTransaction } from "@/service/api-client/api-master";
|
||||
import { colorBadgeTransaction } from "@/utils/colorBadge";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
import { Admin_ScreenDonationListOfDonatur } from "@/screens/Admin/Donation/ScreenDonationListOfDonatur";
|
||||
|
||||
export default function AdminDonasiListOfDonatur() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [listData, setListData] = React.useState<any[] | null>(null);
|
||||
const [loadData, setLoadData] = React.useState(false);
|
||||
const [master, setMaster] = React.useState<any[]>([]);
|
||||
|
||||
const [selectValue, setSelectValue] = React.useState<string | null>(null);
|
||||
const [selectedStatus, setSelectedStatus] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id, selectValue])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
const response = await apiAdminDonationListOfDonatur({
|
||||
id: id as string,
|
||||
status: selectedStatus as any,
|
||||
});
|
||||
// console.log("[LIST OF DONATUR]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setListData([]);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onLoadMaster();
|
||||
}, []);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
const response = await apiMasterTransaction();
|
||||
|
||||
if (response.success) {
|
||||
setMaster(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setMaster([]);
|
||||
}
|
||||
};
|
||||
|
||||
const searchComponent = (
|
||||
<View style={{ flexDirection: "row", gap: 5 }}>
|
||||
<SelectCustom
|
||||
placeholder="Pilih status transaksi"
|
||||
data={
|
||||
_.isEmpty(master)
|
||||
? []
|
||||
: master?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
value={selectValue}
|
||||
onChange={(value: any) => {
|
||||
setSelectValue(value);
|
||||
const nameSelected = master.find((item: any) => item.id === value);
|
||||
const statusChooses = _.lowerCase(nameSelected?.name);
|
||||
setSelectedStatus(statusChooses);
|
||||
}}
|
||||
styleContainer={{ width: "100%", marginBottom: 0 }}
|
||||
allowClear
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
headerComponent={
|
||||
<AdminBackButtonAntTitle newComponent={searchComponent} />
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<GridViewCustomSpan
|
||||
span1={3}
|
||||
span2={5}
|
||||
span3={4}
|
||||
component1={
|
||||
<TextCustom bold align="center">
|
||||
Aksi
|
||||
</TextCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom bold align="center">
|
||||
Donatur
|
||||
</TextCustom>
|
||||
}
|
||||
component3={
|
||||
<TextCustom bold align="center">
|
||||
Status
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<StackCustom>
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item: any, index: number) => (
|
||||
<View key={index}>
|
||||
<GridViewCustomSpan
|
||||
span1={3}
|
||||
span2={5}
|
||||
span3={4}
|
||||
component1={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconView size={ICON_SIZE_BUTTON} color="black" />
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/donation/${item?.id}/${_.lowerCase(
|
||||
item?.DonasiMaster_StatusInvoice?.name
|
||||
)}/transaction-detail`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
component2={
|
||||
<TextCustom bold align="center" truncate>
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
component3={
|
||||
<BadgeCustom
|
||||
style={{ alignSelf: "center" }}
|
||||
color={colorBadgeTransaction({
|
||||
status: item?.DonasiMaster_StatusInvoice?.name,
|
||||
})}
|
||||
>
|
||||
{item?.DonasiMaster_StatusInvoice?.name}
|
||||
</BadgeCustom>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenDonationListOfDonatur />;
|
||||
}
|
||||
|
||||
@@ -1,117 +1,5 @@
|
||||
/* 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 { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
|
||||
import { Octicons } from "@expo/vector-icons";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Divider } from "react-native-paper";
|
||||
import { Admin_ScreenDonationStatus } from "@/screens/Admin/Donation/ScreenDonationStatus";
|
||||
|
||||
export default function AdminDonationStatus() {
|
||||
const { status } = useLocalSearchParams();
|
||||
console.log("[STATUS]", status);
|
||||
|
||||
const [data, setData] = useState<any | null>(null);
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const [loadData, setLoadData] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [status, search])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
const response = await apiAdminDonation({
|
||||
category: status as "publish" | "review" | "reject",
|
||||
search,
|
||||
});
|
||||
|
||||
console.log("[RES]", JSON.stringify(response, null, 2));
|
||||
|
||||
if (response.success) {
|
||||
setData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setData([]);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const rightComponent = (
|
||||
<SearchInput
|
||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||
placeholder="Cari"
|
||||
value={search}
|
||||
onChangeText={(value) => setSearch(value)}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper headerComponent={<AdminTitlePage title="Donasi" />}>
|
||||
<StackCustom gap={"sm"}>
|
||||
<AdminComp_BoxTitle
|
||||
title={`${_.startCase(status as string)}`}
|
||||
rightComponent={rightComponent}
|
||||
/>
|
||||
<AdminTitleTable
|
||||
title1="Aksi"
|
||||
title2="Username"
|
||||
title3="Judul Donasi"
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(data) ? (
|
||||
<TextCustom align="center" size="small" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
data?.map((item: any, index: number) => (
|
||||
<AdminTableValue
|
||||
key={index}
|
||||
value1={
|
||||
<ActionIcon
|
||||
icon={
|
||||
<Octicons
|
||||
name="eye"
|
||||
size={ICON_SIZE_BUTTON}
|
||||
color="black"
|
||||
/>
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/${item.id}/${status}`);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={<TextCustom truncate={1}>{item?.Author?.username || "-"}</TextCustom>}
|
||||
value3={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenDonationStatus />;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ export default function AdminDonationCategoryUpdate() {
|
||||
const response = await apiAdminMasterDonationCategoryById({
|
||||
id: id as any,
|
||||
});
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
|
||||
setData(response.data);
|
||||
};
|
||||
@@ -44,10 +43,9 @@ export default function AdminDonationCategoryUpdate() {
|
||||
id: id as any,
|
||||
data: data,
|
||||
});
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log("Error update category:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,135 +1,5 @@
|
||||
import {
|
||||
BadgeCustom,
|
||||
CenterCustom,
|
||||
ClickableCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { apiAdminMasterDonationCategory } from "@/service/api-admin/api-master-admin";
|
||||
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
import { Admin_ScreenDonationCategory } from "@/screens/Admin/Donation/ScreenDonationCategory";
|
||||
|
||||
export default function AdminDonationCategory() {
|
||||
const [listData, setListData] = useState<any[]>([]);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
fetchMaster();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const fetchMaster = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await apiAdminMasterDonationCategory();
|
||||
if (response.success) {
|
||||
console.log(JSON.stringify(response.data, null, 2));
|
||||
setListData(response.data);
|
||||
} else {
|
||||
setListData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[Error]", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
await fetchMaster();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||
}
|
||||
headerComponent={<AdminTitlePage title="Donasi" />}
|
||||
>
|
||||
<AdminComp_BoxTitle
|
||||
title="Kategori"
|
||||
rightComponent={
|
||||
<AdminActionIconPlus
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/category-create`);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom bold>Status</TextCustom>}
|
||||
value={<TextCustom bold>Kategori</TextCustom>}
|
||||
/>
|
||||
{/* <Grid>
|
||||
<Grid.Col style={{ paddingLeft: 10 }} span={4}>
|
||||
<TextCustom bold>Status</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>Kategori</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid> */}
|
||||
|
||||
<Divider />
|
||||
<Spacing />
|
||||
|
||||
<StackCustom>
|
||||
{listData.map((item, index) => (
|
||||
<ClickableCustom
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/category-update?id=${item.id}`);
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
<GridSpan_4_8
|
||||
label={
|
||||
<CenterCustom>
|
||||
<BadgeCustom
|
||||
color={colorActivationForBadge({
|
||||
status: item.active,
|
||||
})}
|
||||
>
|
||||
{item.active ? "Aktif" : "Tidak Aktif"}
|
||||
</BadgeCustom>
|
||||
</CenterCustom>
|
||||
}
|
||||
value={<TextCustom>{item.name}</TextCustom>}
|
||||
/>
|
||||
{/* <Grid containerStyle={{ paddingBottom: 10 }}>
|
||||
<Grid.Col span={4} style={{ paddingLeft: 10 }}>
|
||||
<CenterCustom>
|
||||
<BadgeCustom
|
||||
color={item.active ? MainColor.green : MainColor.red}
|
||||
>
|
||||
{item.active ? "Aktif" : "Tidak Aktif"}
|
||||
</BadgeCustom>
|
||||
</CenterCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
<TextCustom bold>{item.name}</TextCustom>
|
||||
</Grid.Col>
|
||||
</Grid> */}
|
||||
<Divider />
|
||||
</ClickableCustom>
|
||||
))}
|
||||
</StackCustom>
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenDonationCategory />;
|
||||
}
|
||||
|
||||
@@ -1,129 +1,5 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
BadgeCustom,
|
||||
CenterCustom,
|
||||
LoaderCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { IconEdit } from "@/components/_Icon";
|
||||
import AdminActionIconPlus from "@/components/_ShareComponent/Admin/ActionIconPlus";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTitlePage from "@/components/_ShareComponent/Admin/TitlePage";
|
||||
import { GridViewCustomSpan } from "@/components/_ShareComponent/GridViewCustomSpan";
|
||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
||||
import { apiAdminMasterTypeOfEvent } from "@/service/api-admin/api-master-admin";
|
||||
import { colorActivationForBadge } from "@/utils/colorActivationForBadge";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Divider } from "react-native-paper";
|
||||
import { Admin_ScreenEventTypeOfEvent } from "@/screens/Admin/Voting/ScreenEventTypeOfEvent";
|
||||
|
||||
export default function AdminEventTypeOfEvent() {
|
||||
const [listData, setListData] = useState<any[] | null>(null);
|
||||
const [loadData, setLoadData] = useState<boolean>(false);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadData(true);
|
||||
const response = await apiAdminMasterTypeOfEvent();
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]",error);
|
||||
setListData([]);
|
||||
} finally {
|
||||
setLoadData(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper headerComponent={<AdminTitlePage title="Event" />}>
|
||||
<AdminComp_BoxTitle
|
||||
title="Tipe Acara"
|
||||
rightComponent={
|
||||
<AdminActionIconPlus
|
||||
onPress={() => {
|
||||
router.push(`/admin/event/type-create`);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<>
|
||||
<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>}
|
||||
/>
|
||||
<Divider />
|
||||
<Spacing />
|
||||
|
||||
<StackCustom>
|
||||
{loadData ? (
|
||||
<LoaderCustom />
|
||||
) : _.isEmpty(listData) ? (
|
||||
<TextCustom align="center" color="gray">
|
||||
Belum ada data
|
||||
</TextCustom>
|
||||
) : (
|
||||
listData?.map((item, index) => (
|
||||
<View key={index}>
|
||||
<GridViewCustomSpan
|
||||
span1={2}
|
||||
span2={5}
|
||||
span3={5}
|
||||
component1={
|
||||
<CenterCustom>
|
||||
<ActionIcon
|
||||
icon={
|
||||
<IconEdit size={ICON_SIZE_BUTTON} color="black" />
|
||||
}
|
||||
onPress={() => {
|
||||
router.push(`/admin/event/type-update?id=${item.id}`);
|
||||
}}
|
||||
/>
|
||||
</CenterCustom>
|
||||
}
|
||||
style2={{ alignItems: "center" }}
|
||||
component2={
|
||||
<CenterCustom>
|
||||
<BadgeCustom
|
||||
color={colorActivationForBadge({
|
||||
status: item?.active,
|
||||
})}
|
||||
>
|
||||
{item?.active ? "Aktif" : "Tidak Aktif"}
|
||||
</BadgeCustom>
|
||||
</CenterCustom>
|
||||
}
|
||||
component3={<TextCustom >{item.name}</TextCustom>}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
</>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenEventTypeOfEvent />;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
AlertDefaultSystem,
|
||||
BadgeCustom,
|
||||
BaseBox,
|
||||
ButtonCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
@@ -15,8 +15,8 @@ import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiAdminInvestmentGetOneInvoiceById,
|
||||
apiAdminInvestmentUpdateInvoice,
|
||||
apiAdminInvestmentGetOneInvoiceById,
|
||||
apiAdminInvestmentUpdateInvoice,
|
||||
} from "@/service/api-admin/api-admin-investment";
|
||||
import { colorBadgeTransaction } from "@/utils/colorBadge";
|
||||
import { dateTimeView } from "@/utils/dateTimeView";
|
||||
@@ -158,7 +158,7 @@ export default function AdminInvestmentTransactionDetail() {
|
||||
spanRight={6}
|
||||
styleLeft={{ paddingRight: 10 }}
|
||||
styleRight={{ paddingLeft: 10 }}
|
||||
leftIcon={
|
||||
leftItem={
|
||||
<ButtonCustom
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
@@ -181,7 +181,7 @@ export default function AdminInvestmentTransactionDetail() {
|
||||
Tolak
|
||||
</ButtonCustom>
|
||||
}
|
||||
rightIcon={
|
||||
rightItem={
|
||||
<ButtonCustom
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { ButtonCustom, DrawerCustom, DummyLandscapeImage, Grid, Spacing, StackCustom, TextCustom, ViewWrapper } from "@/components";
|
||||
import {
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
DummyLandscapeImage,
|
||||
Grid,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import API_IMAGE from "@/constants/api-storage";
|
||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||
@@ -45,7 +54,7 @@ export default function AdminMaps() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
handlerLoadList();
|
||||
}, [])
|
||||
}, []),
|
||||
);
|
||||
|
||||
const handlerLoadList = async () => {
|
||||
@@ -144,52 +153,52 @@ export default function AdminMaps() {
|
||||
<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>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import {
|
||||
apiAdminUserAccessGetById,
|
||||
apiAdminUserAccessUpdateStatus,
|
||||
apiAdminUserAccessGetById,
|
||||
apiAdminUserAccessUpdateStatus,
|
||||
} from "@/service/api-admin/api-admin-user-access";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -26,7 +26,7 @@ export default function SuperAdminDetail() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -48,7 +48,7 @@ export default function SuperAdminDetail() {
|
||||
const response = await apiAdminUserAccessUpdateStatus({
|
||||
id: id as string,
|
||||
role: data?.masterUserRoleId === "2" ? "user" : "admin",
|
||||
category: "role"
|
||||
category: "role",
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
@@ -102,8 +102,8 @@ export default function SuperAdminDetail() {
|
||||
key={index}
|
||||
spanLeft={4}
|
||||
spanRight={8}
|
||||
leftIcon={<TextCustom bold>{item?.label}</TextCustom>}
|
||||
rightIcon={<TextCustom>{item?.value}</TextCustom>}
|
||||
leftItem={<TextCustom bold>{item?.label}</TextCustom>}
|
||||
rightItem={<TextCustom>{item?.value}</TextCustom>}
|
||||
/>
|
||||
))}
|
||||
</StackCustom>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
LoaderCustom,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
apiAdminUserAccessGetById,
|
||||
apiAdminUserAccessUpdateStatus,
|
||||
apiAdminUserAccessGetById,
|
||||
apiAdminUserAccessUpdateStatus,
|
||||
} from "@/service/api-admin/api-admin-user-access";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -28,7 +28,7 @@ export default function AdminUserAccessDetail() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -102,8 +102,8 @@ export default function AdminUserAccessDetail() {
|
||||
key={index}
|
||||
spanLeft={4}
|
||||
spanRight={8}
|
||||
leftIcon={<TextCustom bold>{item?.label}</TextCustom>}
|
||||
rightIcon={<TextCustom>{item?.value}</TextCustom>}
|
||||
leftItem={<TextCustom bold>{item?.label}</TextCustom>}
|
||||
rightItem={<TextCustom>{item?.value}</TextCustom>}
|
||||
/>
|
||||
))}
|
||||
</StackCustom>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BaseBox,
|
||||
CircleContainer,
|
||||
Grid,
|
||||
NewWrapper,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
|
||||
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import ReportBox from "@/components/Box/ReportBox";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -40,7 +41,7 @@ export default function AdminVotingDetail() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [id])
|
||||
}, [id]),
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
@@ -169,26 +170,28 @@ export default function AdminVotingDetail() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper
|
||||
<NewWrapper
|
||||
hideFooter
|
||||
headerComponent={<AdminBackButtonAntTitle title={`Detail Data`} />}
|
||||
>
|
||||
<BaseBox>
|
||||
<StackCustom>
|
||||
{listData.map((item, i) => (
|
||||
<GridSpan_4_8
|
||||
<GridTwoView
|
||||
key={i}
|
||||
label={<TextCustom bold>{item.label}</TextCustom>}
|
||||
value={<TextCustom>{item.value}</TextCustom>}
|
||||
spanLeft={5}
|
||||
spanRight={7}
|
||||
leftItem={<TextCustom bold>{item.label}</TextCustom>}
|
||||
rightItem={<TextCustom>{item.value}</TextCustom>}
|
||||
/>
|
||||
))}
|
||||
</StackCustom>
|
||||
</BaseBox>
|
||||
|
||||
{status === "publish" ||
|
||||
(status === "history" && (
|
||||
<BaseBox>
|
||||
<TextCustom bold align="center">
|
||||
Hasil Voting
|
||||
{(status === "publish" || status === "history") && (
|
||||
<BaseBox>
|
||||
<TextCustom bold align="center">
|
||||
Hasil Voting
|
||||
</TextCustom>
|
||||
<Spacing />
|
||||
<Grid>
|
||||
@@ -209,11 +212,11 @@ export default function AdminVotingDetail() {
|
||||
</TextCustom>
|
||||
</StackCustom>
|
||||
</Grid.Col>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
)}
|
||||
|
||||
{data &&
|
||||
data?.catatan &&
|
||||
@@ -250,7 +253,7 @@ export default function AdminVotingDetail() {
|
||||
/>
|
||||
)}
|
||||
<Spacing />
|
||||
</ViewWrapper>
|
||||
</NewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,117 +1,5 @@
|
||||
/* 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, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Divider } from "react-native-paper";
|
||||
import { Admin_ScreenVotingStatus } from "@/screens/Admin/Voting/ScreenVotingStatus";
|
||||
|
||||
export default function AdminVotingStatus() {
|
||||
const { status } = useLocalSearchParams();
|
||||
const [list, setList] = useState<any | null>(null);
|
||||
const [loadList, setLoadList] = useState(false);
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
onLoadData();
|
||||
}, [status, search])
|
||||
);
|
||||
|
||||
const onLoadData = async () => {
|
||||
try {
|
||||
setLoadList(true);
|
||||
const response = await apiAdminVoting({
|
||||
category: status as "publish" | "review" | "reject" as any,
|
||||
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={`${_.startCase(status as string)}`}
|
||||
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}/${status}`);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
value2={
|
||||
<TextCustom truncate={1}>
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
value3={
|
||||
<TextCustom truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenVotingStatus />;
|
||||
}
|
||||
|
||||
@@ -1,104 +1,5 @@
|
||||
/* 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";
|
||||
import { Admin_ScreenVotingHistory } from "@/screens/Admin/Voting/ScreenVotingHistory";
|
||||
|
||||
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 truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
return <Admin_ScreenVotingHistory />;
|
||||
}
|
||||
|
||||
34
bun.lock
34
bun.lock
@@ -45,7 +45,7 @@
|
||||
"moti": "^0.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.4",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-international-phone-number": "^0.9.3",
|
||||
@@ -702,21 +702,21 @@
|
||||
|
||||
"@react-native-firebase/messaging": ["@react-native-firebase/messaging@23.8.6", "", { "peerDependencies": { "@react-native-firebase/app": "23.8.6", "expo": ">=47.0.0" }, "optionalPeers": ["expo"] }, "sha512-48eW4OFl+XKRgxmgwhNCCT7zUGttm3oAxKJxVQ2Iv9o+wVUTQCLELK3ihC/OrTwCWomM9ylIICqKaK4pdmA22Q=="],
|
||||
|
||||
"@react-native/assets-registry": ["@react-native/assets-registry@0.81.4", "", {}, "sha512-AMcDadefBIjD10BRqkWw+W/VdvXEomR6aEZ0fhQRAv7igrBzb4PTn4vHKYg+sUK0e3wa74kcMy2DLc/HtnGcMA=="],
|
||||
"@react-native/assets-registry": ["@react-native/assets-registry@0.81.5", "", {}, "sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w=="],
|
||||
|
||||
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.2", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.2" } }, "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg=="],
|
||||
|
||||
"@react-native/babel-preset": ["@react-native/babel-preset@0.83.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.2", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA=="],
|
||||
|
||||
"@react-native/codegen": ["@react-native/codegen@0.81.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-LWTGUTzFu+qOQnvkzBP52B90Ym3stZT8IFCzzUrppz8Iwglg83FCtDZAR4yLHI29VY/x/+pkcWAMCl3739XHdw=="],
|
||||
"@react-native/codegen": ["@react-native/codegen@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g=="],
|
||||
|
||||
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.81.4", "", { "dependencies": { "@react-native/dev-middleware": "0.81.4", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.1", "metro-config": "^0.83.1", "metro-core": "^0.83.1", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-8mpnvfcLcnVh+t1ok6V9eozWo8Ut+TZhz8ylJ6gF9d6q9EGDQX6s8jenan5Yv/pzN4vQEKI4ib2pTf/FELw+SA=="],
|
||||
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.81.5", "", { "dependencies": { "@react-native/dev-middleware": "0.81.5", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.1", "metro-config": "^0.83.1", "metro-core": "^0.83.1", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-yWRlmEOtcyvSZ4+OvqPabt+NS36vg0K/WADTQLhrYrm9qdZSuXmq8PmdJWz/68wAqKQ+4KTILiq2kjRQwnyhQw=="],
|
||||
|
||||
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.5", "", {}, "sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w=="],
|
||||
|
||||
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.5", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.5", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA=="],
|
||||
|
||||
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.81.4", "", {}, "sha512-T7fPcQvDDCSusZFVSg6H1oVDKb/NnVYLnsqkcHsAF2C2KGXyo3J7slH/tJAwNfj/7EOA2OgcWxfC1frgn9TQvw=="],
|
||||
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.81.5", "", {}, "sha512-hORRlNBj+ReNMLo9jme3yQ6JQf4GZpVEBLxmTXGGlIL78MAezDZr5/uq9dwElSbcGmLEgeiax6e174Fie6qPLg=="],
|
||||
|
||||
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.2", "", {}, "sha512-dk6fIY2OrKW/2Nk2HydfYNrQau8g6LOtd7NVBrgaqa+lvuRyIML5iimShP5qPqQnx2ofHuzjFw+Ya0b5Q7nDbA=="],
|
||||
|
||||
@@ -726,7 +726,7 @@
|
||||
|
||||
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.81.5", "", {}, "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g=="],
|
||||
|
||||
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.81.4", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-hBM+rMyL6Wm1Q4f/WpqGsaCojKSNUBqAXLABNGoWm1vabZ7cSnARMxBvA/2vo3hLcoR4v7zDK8tkKm9+O0LjVA=="],
|
||||
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.81.5", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-UVXgV/db25OPIvwZySeToXD/9sKKhOdkcWmmf4Jh8iBZuyfML+/5CasaZ1E7Lqg6g3uqVQq75NqIwkYmORJMPw=="],
|
||||
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.13.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-qxxjRDpjhZ4vIZqG4rBU1Vx2jgOAO/ciUKc9sJqVlTM005E2E+aK1EaE3lGaBDyZxTpjonollAucZcqL7OTscQ=="],
|
||||
|
||||
@@ -2104,7 +2104,7 @@
|
||||
|
||||
"react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],
|
||||
|
||||
"react-native": ["react-native@0.81.4", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.4", "@react-native/codegen": "0.81.4", "@react-native/community-cli-plugin": "0.81.4", "@react-native/gradle-plugin": "0.81.4", "@react-native/js-polyfills": "0.81.4", "@react-native/normalize-colors": "0.81.4", "@react-native/virtualized-lists": "0.81.4", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.29.1", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "^19.1.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-bt5bz3A/+Cv46KcjV0VQa+fo7MKxs17RCcpzjftINlen4ZDUl0I6Ut+brQ2FToa5oD0IB0xvQHfmsg2EDqsZdQ=="],
|
||||
"react-native": ["react-native@0.81.5", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", "@react-native/codegen": "0.81.5", "@react-native/community-cli-plugin": "0.81.5", "@react-native/gradle-plugin": "0.81.5", "@react-native/js-polyfills": "0.81.5", "@react-native/normalize-colors": "0.81.5", "@react-native/virtualized-lists": "0.81.5", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.29.1", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "^19.1.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw=="],
|
||||
|
||||
"react-native-country-codes-picker": ["react-native-country-codes-picker@2.3.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-dDQhd0bVvlmgb84NPhTOmTk5UVYPHtk3lqZI+BPb61H1rC2IDrTvPWENg6u1DMGliqWHQDBYpeH37zvxxQL71w=="],
|
||||
|
||||
@@ -2688,8 +2688,6 @@
|
||||
|
||||
"@react-native/codegen/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
|
||||
|
||||
"@react-native/community-cli-plugin/@react-native/dev-middleware": ["@react-native/dev-middleware@0.81.4", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.81.4", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^6.2.3" } }, "sha512-hu1Wu5R28FT7nHXs2wWXvQ++7W7zq5GPY83llajgPlYKznyPLAY/7bArc5rAzNB7b0kwnlaoPQKlvD/VP9LZug=="],
|
||||
|
||||
"@react-native/community-cli-plugin/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
|
||||
@@ -2902,9 +2900,7 @@
|
||||
|
||||
"react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"react-native/@react-native/js-polyfills": ["@react-native/js-polyfills@0.81.4", "", {}, "sha512-sr42FaypKXJHMVHhgSbu2f/ZJfrLzgaoQ+HdpRvKEiEh2mhFf6XzZwecyLBvWqf2pMPZa+CpPfNPiejXjKEy8w=="],
|
||||
|
||||
"react-native/@react-native/normalize-colors": ["@react-native/normalize-colors@0.81.4", "", {}, "sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg=="],
|
||||
"react-native/@react-native/js-polyfills": ["@react-native/js-polyfills@0.81.5", "", {}, "sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w=="],
|
||||
|
||||
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||
|
||||
@@ -3062,10 +3058,6 @@
|
||||
|
||||
"@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
|
||||
|
||||
"@react-native/community-cli-plugin/@react-native/dev-middleware/@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.81.4", "", {}, "sha512-SU05w1wD0nKdQFcuNC9D6De0ITnINCi8MEnx9RsTD2e4wN83ukoC7FpXaPCYyP6+VjFt5tUKDPgP1O7iaNXCqg=="],
|
||||
|
||||
"@react-native/community-cli-plugin/@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
|
||||
|
||||
"@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"@testing-library/react-native/pretty-format/@jest/schemas": ["@jest/schemas@30.0.5", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA=="],
|
||||
@@ -3224,8 +3216,6 @@
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"@react-native/community-cli-plugin/@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"@testing-library/react-native/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="],
|
||||
|
||||
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.79.6", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.25.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-iRBX8Lgbqypwnfba7s6opeUwVyaR23mowh9ILw7EcT2oLz3RqMmjJdrbVpWhGSMGq2qkPfqAH7bhO8C7O+xfjQ=="],
|
||||
@@ -3298,8 +3288,6 @@
|
||||
|
||||
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.81.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g=="],
|
||||
|
||||
"logkitty/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
@@ -3326,10 +3314,6 @@
|
||||
|
||||
"babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser": ["hermes-parser@0.29.1", "", { "dependencies": { "hermes-estree": "0.29.1" } }, "sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA=="],
|
||||
|
||||
"logkitty/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
@@ -3343,7 +3327,5 @@
|
||||
"@expo/fingerprint/glob/minimatch/brace-expansion/balanced-match/jackspeak/@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
|
||||
|
||||
"@expo/metro-config/glob/minimatch/brace-expansion/balanced-match/jackspeak/@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
|
||||
|
||||
"expo/babel-preset-expo/@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.29.1", "", {}, "sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import Grid from "../Grid/GridCustom";
|
||||
export default function GridTwoView({
|
||||
spanLeft = 6,
|
||||
spanRight = 6,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
leftItem,
|
||||
rightItem,
|
||||
styleLeft,
|
||||
styleRight,
|
||||
}: {
|
||||
spanLeft?: number;
|
||||
spanRight?: number;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
leftItem?: React.ReactNode;
|
||||
rightItem?: React.ReactNode;
|
||||
styleLeft?: ViewStyle;
|
||||
styleRight?: ViewStyle;
|
||||
}) {
|
||||
@@ -24,13 +24,13 @@ export default function GridTwoView({
|
||||
span={spanLeft}
|
||||
style={styleLeft ? { ...baseStyle, ...styleLeft } : baseStyle}
|
||||
>
|
||||
{leftIcon}
|
||||
{leftItem}
|
||||
</Grid.Col>
|
||||
<Grid.Col
|
||||
span={spanRight}
|
||||
style={styleRight ? { ...baseStyle, ...styleRight } : baseStyle}
|
||||
>
|
||||
{rightIcon}
|
||||
{rightItem}
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
28
docs/CHANGE_LOG.md
Normal file
28
docs/CHANGE_LOG.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# CHANGE LOG - fixed-admin/18-feb-26
|
||||
|
||||
## 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
|
||||
|
||||
## Stats
|
||||
+305 baris, -531 baris (net: -226)
|
||||
@@ -53,18 +53,12 @@ Terapkan NewWrapper pada file: app/(application)/(user)/donation/create.tsx
|
||||
Component yang digunakan: components/_ShareComponent/NewWrapper.tsx
|
||||
<!-- End Penerapan NewWrapper -->
|
||||
|
||||
<!-- Start Random Prompt -->
|
||||
|
||||
|
||||
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.eclar
|
||||
<!-- End Random Prompt -->
|
||||
|
||||
<!-- START Prompt Admin Refactoring -->
|
||||
<!-- Pindah kode ke Screen Component -->
|
||||
File source: app/(application)/admin/event/[id]/list-of-participants.tsx
|
||||
Folder tujuan: screens/Admin/Event
|
||||
Nama file utama: ScreenEventListOfParticipants.tsx
|
||||
Nama function utama: Admin_ScreenEventListOfParticipants
|
||||
File source: app/(application)/admin/donation/[id]/list-disbursement-of-funds.tsx
|
||||
Folder tujuan: screens/Admin/Donation
|
||||
Nama file utama: ScreenDonationListDisbursementOfFunds.tsx
|
||||
Nama function utama: Admin_ScreenDonationListDisbursementOfFunds
|
||||
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
||||
|
||||
Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Nama function utama" kemudian clean code, import dan panggil function tersebut pada file "File source"
|
||||
@@ -72,8 +66,8 @@ Analisa juga file "Nama file utama" , jika belum menggunakan NewWrapper pada fil
|
||||
|
||||
|
||||
<!-- Penerapan Pagination -->
|
||||
Function fecth: apiAdminEventListOfParticipants
|
||||
File function fetch: service/api-admin/api-admin-event.ts
|
||||
Function fecth: apiAdminDonationDisbursementOfFundsListById
|
||||
File function fetch: service/api-admin/api-admin-donation.ts
|
||||
|
||||
Terapkan pagination pada file "Nama file utama"
|
||||
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
|
||||
@@ -81,9 +75,16 @@ Perbaiki fetch "Function fecth" , pada file "File function fetch"
|
||||
Jika tidak ada props page maka tambahkan props page dan default page: "1" ( string )
|
||||
Kemudian rapikan code nya pisah komponen seperti render item dan lainnya agar lebih rapi dan di dalam return panggil komponen tersebut
|
||||
|
||||
|
||||
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
|
||||
<!-- END Prompt Admin Refactoring -->
|
||||
|
||||
<!-- Additional -->
|
||||
File refrensi: screens/Admin/Voting/ScreenEventTypeOfEvent.tsx
|
||||
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang hampir sama
|
||||
|
||||
Untuk refrensi tampilan Box bisa anda gunakan dari file: screens/Admin/Donation/BoxDonationCategory.tsx dan buatkan komponen yang mirip untuk list of donatur dengan nama file: BoxDonationListOfDonatur.tsx
|
||||
|
||||
<!-- Use Prompt Now -->
|
||||
Terapkan NewWrapper pada file: screens/Admin/App-Information/InformationBankSection.tsx
|
||||
Component yang digunakan: components/_ShareComponent/NewWrapper.tsx
|
||||
@@ -95,7 +96,9 @@ Terapkan pagination pada file "Nama file utama"
|
||||
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
|
||||
Perbaiki fetch "Function fecth" , pada file "File function fetch"
|
||||
Jika tidak ada props page maka tambahkan props page dan default page: "1" ( string )
|
||||
<!-- Baru -->
|
||||
|
||||
|
||||
<!-- Create FlatList -->
|
||||
File Utama: screens/Admin/App-Information/InformationBankSection.tsx
|
||||
Terapkan FlatList dan pagination pada file "File Utama"
|
||||
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
|
||||
@@ -103,4 +106,12 @@ Function fecth: apiAdminMasterBank
|
||||
File function fetch: service/api-admin/api-master-admin.ts
|
||||
Jika tidak ada props page maka tambahkan props page dan default page: "1" ( string )
|
||||
Jika butuh refrensi FlatList bisa lihat pada file components/_ShareComponent/NewWrapper.tsx
|
||||
|
||||
<!-- Create Box -->
|
||||
File Utama: screens/Admin/Donation/Admin_ScreenDonationStatus.tsx
|
||||
Folder tujuan: screens/Admin/Donation
|
||||
Buat box component baru pada file "File Utama" di bagian renderItem,
|
||||
|
||||
<!-- END Create Box -->
|
||||
|
||||
<!-- END Use Prompt Now -->
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20</string>
|
||||
<string>21</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"moti": "^0.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.4",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-international-phone-number": "^0.9.3",
|
||||
|
||||
36
screens/Admin/Donation/BoxDonationCategory.tsx
Normal file
36
screens/Admin/Donation/BoxDonationCategory.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { TextCustom, BadgeCustom } from "@/components";
|
||||
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function Admin_BoxDonationCategory({item}: {item: any}) {
|
||||
return (
|
||||
<>
|
||||
<AdminBasicBox
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/category-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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Divider, StackCustom, TextCustom } from "@/components";
|
||||
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import dayjs from "dayjs";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
|
||||
interface BoxDonationListDisbursementOfFundsProps {
|
||||
item: any;
|
||||
}
|
||||
|
||||
export default function Admin_BoxDonationListDisbursementOfFunds({
|
||||
item,
|
||||
}: BoxDonationListDisbursementOfFundsProps) {
|
||||
return (
|
||||
<>
|
||||
<AdminBasicBox
|
||||
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/donation/${item?.id}/detail-disbursement-of-funds`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<StackCustom gap={0}>
|
||||
<View style={{ paddingBlock: 8 }}>
|
||||
<TextCustom size={"large"} bold truncate>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
<Divider />
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Tanggal</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
{dayjs(item?.createdAt).format("DD-MM-YYYY") || "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Nominal</TextCustom>}
|
||||
value={
|
||||
<TextCustom bold>
|
||||
Rp {formatCurrencyDisplay(item?.nominalCair)}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</AdminBasicBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
68
screens/Admin/Donation/BoxDonationListOfDonatur.tsx
Normal file
68
screens/Admin/Donation/BoxDonationListOfDonatur.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
BadgeCustom,
|
||||
Divider,
|
||||
StackCustom,
|
||||
TextCustom
|
||||
} from "@/components";
|
||||
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||
import { colorBadgeTransaction } from "@/utils/colorBadge";
|
||||
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { View } from "react-native";
|
||||
|
||||
interface BoxDonationListOfDonaturProps {
|
||||
item: any;
|
||||
}
|
||||
|
||||
export default function Admin_BoxDonationListOfDonatur({
|
||||
item,
|
||||
}: BoxDonationListOfDonaturProps) {
|
||||
const statusName = item?.DonasiMaster_StatusInvoice?.name || "-";
|
||||
return (
|
||||
<>
|
||||
<AdminBasicBox
|
||||
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||
onPress={() => {
|
||||
router.push(
|
||||
`/admin/donation/${item?.id}/${_.lowerCase(
|
||||
item?.DonasiMaster_StatusInvoice?.name,
|
||||
)}/transaction-detail`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<StackCustom gap={0}>
|
||||
<View style={{ paddingBlock: 8 }}>
|
||||
<TextCustom size={"large"} bold truncate>
|
||||
{item?.Author?.username || "-"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
<Divider />
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Status</TextCustom>}
|
||||
value={
|
||||
<BadgeCustom
|
||||
color={colorBadgeTransaction({
|
||||
status: statusName,
|
||||
})}
|
||||
>
|
||||
{statusName}
|
||||
</BadgeCustom>
|
||||
}
|
||||
/>
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Nominal</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
{item?.nominal
|
||||
? `Rp ${formatCurrencyDisplay(item?.nominal)}`
|
||||
: "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</AdminBasicBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
54
screens/Admin/Donation/BoxDonationStatus.tsx
Normal file
54
screens/Admin/Donation/BoxDonationStatus.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 { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
|
||||
import { router } from "expo-router";
|
||||
import { View } from "react-native";
|
||||
|
||||
interface BoxDonationStatusProps {
|
||||
item: any;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export default function Admin_BoxDonationStatus({
|
||||
item,
|
||||
status,
|
||||
}: BoxDonationStatusProps) {
|
||||
return (
|
||||
<>
|
||||
<AdminBasicBox
|
||||
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||
onPress={() => {
|
||||
router.push(`/admin/donation/${item.id}/${status}`);
|
||||
}}
|
||||
>
|
||||
<StackCustom gap={0}>
|
||||
<View style={{ paddingBlock: 8 }}>
|
||||
<TextCustom size={"large"} bold truncate={2}>
|
||||
{item?.title || "-"}
|
||||
</TextCustom>
|
||||
</View>
|
||||
<Divider />
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Durasi</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
{item?.DonasiMaster_Durasi?.name || "-"} hari
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
<GridSpan_4_8
|
||||
label={<TextCustom>Target</TextCustom>}
|
||||
value={
|
||||
<TextCustom>
|
||||
{item?.target
|
||||
? `Rp ${formatCurrencyDisplay(item?.target)}`
|
||||
: "-"}
|
||||
</TextCustom>
|
||||
}
|
||||
/>
|
||||
</StackCustom>
|
||||
</AdminBasicBox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
107
screens/Admin/Donation/ScreenDonationCategory.tsx
Normal file
107
screens/Admin/Donation/ScreenDonationCategory.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
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 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 { apiAdminMasterDonationCategory } 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";
|
||||
import Admin_BoxDonationCategory from "./BoxDonationCategory";
|
||||
|
||||
export function Admin_ScreenDonationCategory() {
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminMasterDonationCategory({
|
||||
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/donation/category-create`);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Header component untuk title
|
||||
const headerComponent = useMemo(
|
||||
() => (
|
||||
<AdminComp_BoxTitle
|
||||
title="Kategori Donasi"
|
||||
rightComponent={rightComponent}
|
||||
/>
|
||||
),
|
||||
[rightComponent],
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
pagination.onRefresh();
|
||||
}, []),
|
||||
);
|
||||
|
||||
// Render item untuk daftar kategori donasi
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<Admin_BoxDonationCategory key={index} item={item} />
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// 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"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
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 { apiAdminDonationDisbursementOfFundsListById } from "@/service/api-admin/api-admin-donation";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Admin_BoxDonationListDisbursementOfFunds from "./BoxDonationListDisbursementOfFunds";
|
||||
|
||||
export function Admin_ScreenDonationListDisbursementOfFunds() {
|
||||
const { id } = useLocalSearchParams();
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminDonationDisbursementOfFundsListById({
|
||||
id: id as string,
|
||||
category: "get-all",
|
||||
page: String(page),
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return { data: response.data };
|
||||
} else {
|
||||
return { data: [] };
|
||||
}
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
searchQuery: "",
|
||||
dependencies: [id],
|
||||
});
|
||||
|
||||
// Header component dengan back button dan title
|
||||
const headerComponent = useMemo(
|
||||
() => <AdminBackButtonAntTitle title="Daftar Pencairan Dana" />,
|
||||
[],
|
||||
);
|
||||
|
||||
// Render item untuk daftar pencairan dana
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<Admin_BoxDonationListDisbursementOfFunds key={index} item={item} />
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Buat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: "",
|
||||
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"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
134
screens/Admin/Donation/ScreenDonationListOfDonatur.tsx
Normal file
134
screens/Admin/Donation/ScreenDonationListOfDonatur.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { SelectCustom } from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
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 { apiAdminDonationListOfDonatur } from "@/service/api-admin/api-admin-donation";
|
||||
import { apiMasterTransaction } from "@/service/api-client/api-master";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
import Admin_BoxDonationListOfDonatur from "./BoxDonationListOfDonatur";
|
||||
|
||||
export function Admin_ScreenDonationListOfDonatur() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [selectValue, setSelectValue] = useState<string | null>(null);
|
||||
const [selectedStatus, setSelectedStatus] = useState<string | null>(null);
|
||||
const [master, setMaster] = useState<any[]>([]);
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminDonationListOfDonatur({
|
||||
id: id as string,
|
||||
status: selectedStatus as any,
|
||||
page: String(page),
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
return { data: response.data };
|
||||
} else {
|
||||
return { data: [] };
|
||||
}
|
||||
},
|
||||
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||
searchQuery: "",
|
||||
dependencies: [id, selectedStatus],
|
||||
});
|
||||
|
||||
// Load master data untuk select option
|
||||
useEffect(() => {
|
||||
onLoadMaster();
|
||||
}, []);
|
||||
|
||||
const onLoadMaster = async () => {
|
||||
try {
|
||||
const response = await apiMasterTransaction();
|
||||
if (response.success) {
|
||||
setMaster(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
setMaster([]);
|
||||
}
|
||||
};
|
||||
|
||||
// Komponen select untuk filter status
|
||||
const searchComponent = useMemo(
|
||||
() => (
|
||||
<View style={{ flexDirection: "row", gap: 5 }}>
|
||||
<SelectCustom
|
||||
placeholder="Pilih status transaksi"
|
||||
data={
|
||||
_.isEmpty(master)
|
||||
? []
|
||||
: master?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
}
|
||||
value={selectValue}
|
||||
onChange={(value: any) => {
|
||||
setSelectValue(value);
|
||||
const nameSelected = master.find((item: any) => item.id === value);
|
||||
const statusChooses = _.lowerCase(nameSelected?.name);
|
||||
setSelectedStatus(statusChooses);
|
||||
}}
|
||||
styleContainer={{ width: "100%", marginBottom: 0 }}
|
||||
allowClear
|
||||
/>
|
||||
</View>
|
||||
),
|
||||
[master, selectValue],
|
||||
);
|
||||
|
||||
// Header component dengan back button dan select filter
|
||||
const headerComponent = useMemo(
|
||||
() => <AdminBackButtonAntTitle newComponent={searchComponent} />,
|
||||
[searchComponent],
|
||||
);
|
||||
|
||||
// Render item untuk daftar donatur
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<Admin_BoxDonationListOfDonatur key={index} item={item} />
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Buat komponen-komponen pagination
|
||||
const { ListEmptyComponent, ListFooterComponent } =
|
||||
createPaginationComponents({
|
||||
loading: pagination.loading,
|
||||
refreshing: pagination.refreshing,
|
||||
listData: pagination.listData,
|
||||
searchQuery: "",
|
||||
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"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
112
screens/Admin/Donation/ScreenDonationStatus.tsx
Normal file
112
screens/Admin/Donation/ScreenDonationStatus.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { ActionIcon, SearchInput, TextCustom } from "@/components";
|
||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
||||
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||
import {
|
||||
ICON_SIZE_BUTTON,
|
||||
PAGINATION_DEFAULT_TAKE,
|
||||
} from "@/constants/constans-value";
|
||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||
import { usePagination } from "@/hooks/use-pagination";
|
||||
import { apiAdminDonation } from "@/service/api-admin/api-admin-donation";
|
||||
import { Octicons } from "@expo/vector-icons";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { RefreshControl } from "react-native";
|
||||
import Admin_BoxDonationStatus from "./BoxDonationStatus";
|
||||
|
||||
export function Admin_ScreenDonationStatus() {
|
||||
const { status } = useLocalSearchParams();
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
// Gunakan hook pagination
|
||||
const pagination = usePagination({
|
||||
fetchFunction: async (page, searchQuery) => {
|
||||
const response = await apiAdminDonation({
|
||||
category: status as "publish" | "review" | "reject",
|
||||
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 judul donasi"
|
||||
value={search}
|
||||
onChangeText={(value) => setSearch(value)}
|
||||
/>
|
||||
),
|
||||
[search],
|
||||
);
|
||||
|
||||
// Render item untuk daftar donasi
|
||||
const renderItem = useCallback(
|
||||
({ item, index }: { item: any; index: number }) => (
|
||||
<Admin_BoxDonationStatus
|
||||
key={index}
|
||||
item={item}
|
||||
status={status as string}
|
||||
/>
|
||||
),
|
||||
[status],
|
||||
);
|
||||
|
||||
// Header component dengan judul status donasi
|
||||
const headerComponent = useMemo(
|
||||
() => (
|
||||
<AdminComp_BoxTitle
|
||||
title={`Donasi ${_.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: 120,
|
||||
});
|
||||
|
||||
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"]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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 Event" 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>
|
||||
|
||||
@@ -4,13 +4,15 @@ import { apiConfig } from "../api-config";
|
||||
export async function apiAdminDonation({
|
||||
category,
|
||||
search,
|
||||
page = "1",
|
||||
}: {
|
||||
category: "dashboard" | "publish" | "review" | "reject";
|
||||
search?: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/admin/donation?category=${category}&search=${search}`
|
||||
`/mobile/admin/donation?category=${category}&search=${search}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -131,12 +133,16 @@ export async function apiAdminDonationDisbursementOfFundsCreated({
|
||||
export async function apiAdminDonationDisbursementOfFundsListById({
|
||||
id,
|
||||
category,
|
||||
page = "1",
|
||||
}: {
|
||||
id: string;
|
||||
category: "get-all" | "get-one"
|
||||
category: "get-all" | "get-one";
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/admin/donation/${id}/disbursement?category=${category}`);
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/admin/donation/${id}/disbursement?category=${category}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -4,13 +4,15 @@ import { apiConfig } from "../api-config";
|
||||
export async function apiAdminVoting({
|
||||
category,
|
||||
search,
|
||||
page = "1",
|
||||
}: {
|
||||
category: "dashboard" | "history" | "publish" | "review" | "report";
|
||||
search?: string;
|
||||
page?: string;
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.get(
|
||||
`/mobile/admin/voting?category=${category}&search=${search}`
|
||||
`/mobile/admin/voting?category=${category}&search=${search}&page=${page}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
|
||||
@@ -135,9 +135,9 @@ export async function apiAdminMasterBusinessFieldCreate({
|
||||
// ================== END BUSINNES FIELD ================== //
|
||||
|
||||
// ================== START EVENT ================== //
|
||||
export async function apiAdminMasterTypeOfEvent() {
|
||||
export async function apiAdminMasterTypeOfEvent({ page = "1" }: { page?: string }) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/admin/master/type-of-event`);
|
||||
const response = await apiConfig.get(`/mobile/admin/master/type-of-event?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -193,9 +193,9 @@ export async function apiAdminMasterTypeOfEventUpdate({
|
||||
|
||||
// ================== START DONATION ================== //
|
||||
|
||||
export async function apiAdminMasterDonationCategory() {
|
||||
export async function apiAdminMasterDonationCategory({ page = "1" }: { page?: string }) {
|
||||
try {
|
||||
const response = await apiConfig.get(`/mobile/admin/master/donation`);
|
||||
const response = await apiConfig.get(`/mobile/admin/master/donation?page=${page}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user