Merge pull request 'fix-ios/25-feb-26' (#56) from fix-ios/25-feb-26 into staging
Reviewed-on: #56
This commit is contained in:
512
QWEN.md
512
QWEN.md
@@ -2,56 +2,68 @@
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named "HIPMI Badung Connect" and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase.
|
HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named **"HIPMI Badung Connect"** and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase.
|
||||||
|
|
||||||
### Key Technologies
|
### Key Technologies
|
||||||
- **Framework**: Expo (v54.0.0) with React Native (v0.81.4)
|
- **Framework**: Expo (v54.0.0) with React Native (v0.81.5)
|
||||||
- **Language**: TypeScript
|
- **Language**: TypeScript
|
||||||
- **Architecture**: File-based routing with Expo Router
|
- **Architecture**: File-based routing with Expo Router
|
||||||
- **State Management**: Context API
|
- **State Management**: Context API (AuthContext)
|
||||||
- **UI Components**: React Native Paper, custom components
|
- **UI Components**: React Native Paper, custom components
|
||||||
- **Maps Integration**: Mapbox Maps for React Native
|
- **Maps Integration**: Mapbox Maps for React Native
|
||||||
- **Push Notifications**: React Native Firebase Messaging
|
- **Push Notifications**: React Native Firebase Messaging
|
||||||
- **Build System**: Metro bundler
|
- **Build System**: Metro bundler
|
||||||
|
- **Package Manager**: Bun
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
```
|
```
|
||||||
hipmi-mobile/
|
hipmi-mobile/
|
||||||
├── app/ # Main application screens and routing
|
├── app/ # Main application screens and routing (Expo Router)
|
||||||
│ ├── _layout.tsx # Root layout component
|
│ ├── _layout.tsx # Root layout component
|
||||||
│ ├── index.tsx # Entry point (Login screen)
|
│ ├── index.tsx # Entry point (Login screen)
|
||||||
|
│ └── (application)/ # Main app screens
|
||||||
|
│ ├── admin/ # Admin panel screens
|
||||||
|
│ ├── (user)/ # User screens
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── components/ # Reusable UI components
|
├── components/ # Reusable UI components
|
||||||
|
│ ├── _ShareComponent/ # Shared components (NewWrapper, Admin components)
|
||||||
|
│ ├── _Icon/ # Icon components
|
||||||
|
│ └── ...
|
||||||
├── context/ # State management (AuthContext)
|
├── context/ # State management (AuthContext)
|
||||||
├── screens/ # Screen components organized by feature
|
├── screens/ # Screen components organized by feature
|
||||||
│ ├── Admin/ # Admin panel screens
|
│ ├── Admin/ # Admin panel screens
|
||||||
|
│ │ ├── Donation/ # Donation management screens
|
||||||
|
│ │ ├── Voting/ # Voting management screens
|
||||||
|
│ │ ├── Event/ # Event management screens
|
||||||
|
│ │ └── ...
|
||||||
│ ├── Authentication/ # Login, registration flows
|
│ ├── Authentication/ # Login, registration flows
|
||||||
│ ├── Collaboration/ # Collaboration features
|
│ ├── RootLayout/ # Root layout components
|
||||||
│ ├── Event/ # Event management
|
|
||||||
│ ├── Forum/ # Forum functionality
|
|
||||||
│ ├── Home/ # Home screen
|
|
||||||
│ ├── Maps/ # Map integration
|
|
||||||
│ ├── Profile/ # User profile
|
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── 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
|
|
||||||
├── service/ # API services and business logic
|
├── service/ # API services and business logic
|
||||||
|
│ ├── api-admin/ # Admin API endpoints
|
||||||
|
│ ├── api-client/ # Client API endpoints
|
||||||
|
│ └── api-config.ts # Axios configuration
|
||||||
|
├── hooks/ # Custom React hooks
|
||||||
|
│ ├── use-pagination.tsx # Pagination hook
|
||||||
|
│ └── ...
|
||||||
|
├── helpers/ # Helper functions
|
||||||
|
│ ├── paginationHelpers.tsx # Pagination UI helpers
|
||||||
|
│ └── ...
|
||||||
├── types/ # TypeScript type definitions
|
├── types/ # TypeScript type definitions
|
||||||
└── utils/ # Helper functions
|
├── utils/ # Utility functions
|
||||||
|
├── constants/ # Constants and configuration values
|
||||||
|
├── styles/ # Global styles
|
||||||
|
├── assets/ # Images, icons, and static assets
|
||||||
|
└── docs/ # Documentation files
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building and Running
|
## Building and Running
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- Node.js (with bun as the package manager)
|
- **Node.js**: v18+ with Bun package manager
|
||||||
- Expo CLI
|
- **Expo CLI**: Installed globally or via npx
|
||||||
- iOS Simulator or Android Emulator (for native builds)
|
- **iOS**: Xcode (macOS only) for iOS simulator/builds
|
||||||
- Android Studio (for Android builds)
|
- **Android**: Android Studio for Android emulator/builds
|
||||||
- Xcode (for iOS builds, macOS only)
|
|
||||||
|
|
||||||
### Setup and Development
|
### Setup and Development
|
||||||
|
|
||||||
@@ -63,16 +75,27 @@ hipmi-mobile/
|
|||||||
2. **Run Development Server**
|
2. **Run Development Server**
|
||||||
```bash
|
```bash
|
||||||
bun run start
|
bun run start
|
||||||
```
|
# or
|
||||||
Or use the shorthand:
|
|
||||||
```bash
|
|
||||||
bunx expo start
|
bunx expo start
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Platform-Specific Commands**
|
3. **Platform-Specific Commands**
|
||||||
- iOS: `bun run ios` or `bunx expo start --ios`
|
```bash
|
||||||
- Android: `bun run android` or `bunx expo start --android`
|
# iOS Simulator
|
||||||
- Web: `bun run web` or `bunx expo start --web`
|
bun run ios
|
||||||
|
# or
|
||||||
|
bunx expo start --ios
|
||||||
|
|
||||||
|
# Android Emulator
|
||||||
|
bun run android
|
||||||
|
# or
|
||||||
|
bunx expo start --android
|
||||||
|
|
||||||
|
# Web Browser
|
||||||
|
bun run web
|
||||||
|
# or
|
||||||
|
bunx expo start --web
|
||||||
|
```
|
||||||
|
|
||||||
4. **Linting**
|
4. **Linting**
|
||||||
```bash
|
```bash
|
||||||
@@ -83,13 +106,13 @@ hipmi-mobile/
|
|||||||
|
|
||||||
#### EAS Build (Production)
|
#### EAS Build (Production)
|
||||||
```bash
|
```bash
|
||||||
# Production build
|
# Production build (App Store / Play Store)
|
||||||
eas build --profile production
|
eas build --profile production
|
||||||
|
|
||||||
# Preview build
|
# Preview build (Internal distribution)
|
||||||
eas build --profile preview
|
eas build --profile preview
|
||||||
|
|
||||||
# Development build
|
# Development build (Development client)
|
||||||
eas build --profile development
|
eas build --profile development
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -100,7 +123,7 @@ npx expo prebuild
|
|||||||
|
|
||||||
# iOS specific
|
# iOS specific
|
||||||
bunx expo prebuild --platform ios
|
bunx expo prebuild --platform ios
|
||||||
open ios/HIPMIBADUNG.xcworkspace
|
open ios/HIPMIBadungConnect.xcworkspace
|
||||||
|
|
||||||
# Android specific
|
# Android specific
|
||||||
bunx expo prebuild --platform android
|
bunx expo prebuild --platform android
|
||||||
@@ -110,6 +133,12 @@ bunx expo prebuild --platform android
|
|||||||
```bash
|
```bash
|
||||||
# Patch version update
|
# Patch version update
|
||||||
npm version patch
|
npm version patch
|
||||||
|
|
||||||
|
# Update iOS build number
|
||||||
|
bunx expo prebuild --platform ios
|
||||||
|
|
||||||
|
# Update Android version code
|
||||||
|
bunx expo prebuild --platform android
|
||||||
```
|
```
|
||||||
|
|
||||||
### Android Debugging
|
### Android Debugging
|
||||||
@@ -126,157 +155,336 @@ adb -s <device_id> install android/app/build/outputs/apk/debug/app-debug.apk
|
|||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
The application uses environment variables defined in the `app.config.js` file:
|
Create a `.env` file in the project root with:
|
||||||
- `API_BASE_URL`: Base URL for API endpoints
|
|
||||||
- `BASE_URL`: Base application URL
|
|
||||||
- `DEEP_LINK_URL`: URL for deep linking functionality
|
|
||||||
|
|
||||||
Create a `.env` file in the project root with these variables.
|
```env
|
||||||
|
API_BASE_URL=https://your-api-base-url.com
|
||||||
|
BASE_URL=https://your-app-url.com
|
||||||
|
DEEP_LINK_URL=hipmimobile://
|
||||||
|
```
|
||||||
|
|
||||||
## EAS Build Configuration
|
These are loaded in `app.config.js` and accessible via `Constants.expoConfig.extra`.
|
||||||
|
|
||||||
The project uses Expo Application Services (EAS) for building and deploying:
|
## Architecture Patterns
|
||||||
- **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`.
|
### 1. Separation of Concerns
|
||||||
|
|
||||||
## Features and Functionality
|
**Route Files** (`app/`) should be minimal (max 5 lines):
|
||||||
|
```typescript
|
||||||
|
import { Admin_ScreenXXX } from "@/screens/Admin/XXX/ScreenXXX";
|
||||||
|
|
||||||
The application includes several key modules:
|
export default function AdminXXX() {
|
||||||
- **Authentication**: Login with phone number, OTP verification, registration, terms acceptance
|
return <Admin_ScreenXXX />;
|
||||||
- **Admin Panel**: Administrative functions for managing content and users
|
}
|
||||||
- **Collaboration**: Tools for member collaboration
|
```
|
||||||
- **Events**: Event management and calendar
|
|
||||||
- **Forum**: Discussion forums
|
**Screen Components** (`screens/`) contain all business logic:
|
||||||
- **Maps**: Location-based services with Mapbox integration
|
```typescript
|
||||||
- **Donations**: Donation functionality with fund disbursement tracking
|
export function Admin_ScreenXXX() {
|
||||||
- **Job Board**: Employment opportunities
|
// Logic, hooks, state management
|
||||||
- **Investment**: Investment-related features
|
return <NewWrapper ... />;
|
||||||
- **Voting**: Voting systems
|
}
|
||||||
- **Portfolio**: Member portfolio showcase
|
```
|
||||||
- **Notifications**: Push notifications via Firebase
|
|
||||||
|
### 2. Pagination Pattern
|
||||||
|
|
||||||
|
Using `usePagination` hook with infinite scroll:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page, searchQuery) => {
|
||||||
|
const response = await apiXXX({ page: String(page) });
|
||||||
|
if (response.success) {
|
||||||
|
return { data: response.data };
|
||||||
|
}
|
||||||
|
return { data: [] };
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE, // 10
|
||||||
|
searchQuery: search,
|
||||||
|
dependencies: [dependency],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
emptyMessage: "Belum ada data",
|
||||||
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Wrapper Components
|
||||||
|
|
||||||
|
**NewWrapper** (preferred for lists):
|
||||||
|
```typescript
|
||||||
|
<NewWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderItem}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
refreshControl={<RefreshControl ... />}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**AdminBasicBox** (for card layouts):
|
||||||
|
```typescript
|
||||||
|
<AdminBasicBox
|
||||||
|
onPress={() => router.push(`/path/${item.id}`)}
|
||||||
|
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<GridSpan_4_8 label="Label" value={<TextCustom>Value</TextCustom>} />
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. API Service Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// service/api-admin/api-xxx.ts
|
||||||
|
export async function apiXXX({ page = "1" }: { page?: string }) {
|
||||||
|
try {
|
||||||
|
const response = await apiConfig.get(`/mobile/admin/xxx?page=${page}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: All list APIs should support pagination with `page` parameter (default: "1").
|
||||||
|
|
||||||
|
### 5. Authentication Flow
|
||||||
|
|
||||||
|
Managed by `AuthContext`:
|
||||||
|
- `loginWithNomor()` - Send phone number, receive OTP
|
||||||
|
- `validateOtp()` - Validate OTP, get token
|
||||||
|
- `registerUser()` - Register new user
|
||||||
|
- `logout()` - Clear session and logout
|
||||||
|
- `userData()` - Fetch user data by token
|
||||||
|
|
||||||
## Development Conventions
|
## Development Conventions
|
||||||
|
|
||||||
### Coding Standards
|
### Coding Standards
|
||||||
- TypeScript is used throughout the project for type safety
|
- **TypeScript**: Strict mode enabled
|
||||||
- Component-based architecture with reusable components
|
- **Naming**:
|
||||||
- Context API for state management (AuthContext)
|
- Components: PascalCase (`Admin_ScreenDonationStatus`)
|
||||||
- File-based routing with Expo Router
|
- Files: PascalCase for components (`ScreenDonationStatus.tsx`)
|
||||||
- Consistent naming conventions using camelCase for variables and PascalCase for components
|
- Variables: camelCase
|
||||||
- Path aliases: `@/*` maps to project root
|
- Constants: UPPER_SNAKE_CASE
|
||||||
|
- **Path Aliases**: `@/*` maps to project root
|
||||||
|
- **Imports**: Group imports by type (components, hooks, services, etc.)
|
||||||
|
|
||||||
### Architecture Patterns
|
### Component Structure
|
||||||
|
```typescript
|
||||||
|
// 1. Imports (grouped)
|
||||||
|
import { ... } from "@/components";
|
||||||
|
import { ... } from "@/hooks";
|
||||||
|
import { ... } from "@/service";
|
||||||
|
|
||||||
#### Screen Components
|
// 2. Types/Interfaces
|
||||||
- Screen components are stored in `/screens` directory organized by feature
|
interface Props { ... }
|
||||||
- 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() {
|
// 3. Main Component
|
||||||
return <SomeScreen />;
|
export function ComponentName() {
|
||||||
|
// State
|
||||||
|
// Hooks
|
||||||
|
// Functions
|
||||||
|
// Render
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 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
|
### Testing
|
||||||
- Linting is configured with ESLint
|
- Linting: `bun run lint`
|
||||||
- Standard Expo linting configuration
|
- No formal test suite configured yet
|
||||||
|
|
||||||
### Security
|
### Git Workflow
|
||||||
- Firebase is integrated for authentication and messaging
|
- Feature branches: `feature/xxx` or `fixed-admin/xxx`
|
||||||
- Camera and location permissions are properly configured
|
- Commit messages: Clear and descriptive
|
||||||
- Deep linking is secured with app domain associations
|
- Use CHANGE_LOG.md for tracking changes
|
||||||
- Auth tokens stored in AsyncStorage
|
|
||||||
|
|
||||||
## Key Dependencies
|
## Key Features
|
||||||
|
|
||||||
### Core Dependencies
|
### Authentication
|
||||||
- `@react-navigation/*`: Navigation solution for React Native
|
- Phone number login with OTP
|
||||||
- `@react-native-firebase/*`: Firebase integration for React Native
|
- User registration
|
||||||
- `@rnmapbox/maps`: Mapbox integration for React Native
|
- Terms & Conditions acceptance
|
||||||
- `expo-router`: File-based routing for Expo applications
|
- Session persistence with AsyncStorage
|
||||||
- `react-native-paper`: Material Design components for React Native
|
|
||||||
- `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
|
### Admin Module
|
||||||
- `@types/*`: TypeScript type definitions
|
- **Dashboard**: Overview and statistics
|
||||||
- `eslint-config-expo`: Expo-specific ESLint configuration
|
- **User Access**: User management
|
||||||
- `typescript`: Type checking
|
- **Event**: Event CRUD with status management
|
||||||
|
- **Voting**: Voting management (publish/review/reject)
|
||||||
|
- **Donation**: Donation management with categories and transaction tracking
|
||||||
|
- **Collaboration**: Collaboration requests
|
||||||
|
- **Investment**: Investment management
|
||||||
|
- **Maps**: Location-based features
|
||||||
|
- **App Information**: Bank and business field management
|
||||||
|
|
||||||
## Platform Support
|
### User Module
|
||||||
|
- **Home**: Main dashboard
|
||||||
|
- **Forum**: Discussion forums
|
||||||
|
- **Profile**: User profile management
|
||||||
|
- **Portfolio**: Member portfolio
|
||||||
|
- **Notifications**: Push notifications via Firebase
|
||||||
|
|
||||||
The application is configured to support:
|
## API Configuration
|
||||||
- **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
|
### Base URLs
|
||||||
|
```typescript
|
||||||
|
// From app.config.js extra
|
||||||
|
API_BASE_URL: process.env.API_BASE_URL
|
||||||
|
BASE_URL: process.env.BASE_URL
|
||||||
|
DEEP_LINK_URL: process.env.DEEP_LINK_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Axios Interceptor
|
||||||
|
All API calls use `apiConfig` with automatic token injection:
|
||||||
|
```typescript
|
||||||
|
apiConfig.interceptors.request.use(async (config) => {
|
||||||
|
const token = await AsyncStorage.getItem("authToken");
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform Configuration
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
- **Bundle ID**: `com.anonymous.hipmi-mobile`
|
||||||
|
- **Build Number**: 21
|
||||||
|
- **Google Services**: Configured
|
||||||
|
- **Associated Domains**: `applinks:cld-dkr-staging-hipmi.wibudev.com`
|
||||||
|
- **Tablet Support**: Enabled
|
||||||
|
|
||||||
|
### Android
|
||||||
|
- **Package**: `com.bip.hipmimobileapp`
|
||||||
|
- **Version Code**: 4
|
||||||
|
- **Google Services**: Configured (`google-services.json`)
|
||||||
|
- **Deep Links**: HTTPS intent filters configured
|
||||||
|
- **Edge-to-Edge**: Enabled
|
||||||
|
|
||||||
|
### Web
|
||||||
|
- **Output**: Static
|
||||||
|
- **Bundler**: Metro
|
||||||
|
|
||||||
|
## Special Integrations
|
||||||
|
|
||||||
|
### Firebase
|
||||||
|
- Authentication
|
||||||
|
- Push Notifications (FCM)
|
||||||
|
- Configured for both iOS and Android
|
||||||
|
|
||||||
|
### Mapbox
|
||||||
|
- Map integration via `@rnmapbox/maps`
|
||||||
|
- Location permissions configured
|
||||||
|
|
||||||
### Deep Linking
|
### Deep Linking
|
||||||
- Scheme: `hipmimobile://`
|
- Scheme: `hipmimobile://`
|
||||||
- Associated domains: `applinks:cld-dkr-staging-hipmi.wibudev.com`
|
- HTTPS: `cld-dkr-staging-hipmi.wibudev.com`
|
||||||
- Configured for both iOS and Android
|
- Configured for both platforms
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
### Camera
|
### Camera
|
||||||
Camera permissions configured for both iOS and Android with microphone access for recording.
|
- Camera and microphone permissions
|
||||||
|
- QR code generation support
|
||||||
|
|
||||||
## Common Development Tasks
|
## Common Development Tasks
|
||||||
|
|
||||||
### Adding a New Screen
|
### Adding a New Admin 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. **Create Screen Component** (`screens/Admin/Feature/ScreenXXX.tsx`):
|
||||||
1. Add function in appropriate service file (`service/api-client/` or `service/api-admin/`)
|
```typescript
|
||||||
2. Use `apiConfig` axios instance for requests
|
export function Admin_ScreenXXX() {
|
||||||
3. Include proper error handling
|
const pagination = usePagination({...});
|
||||||
|
const renderItem = useCallback(...);
|
||||||
|
const headerComponent = useMemo(...);
|
||||||
|
|
||||||
### Refactoring Pattern (from docs/prompt-for-qwen-code.md)
|
return <NewWrapper ... />;
|
||||||
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
|
2. **Create Box Component** (optional, for custom item rendering):
|
||||||
4. Apply pagination helpers if displaying lists
|
```typescript
|
||||||
5. Import and call from original route file
|
export default function Admin_BoxXXX({ item }: { item: any }) {
|
||||||
|
return (
|
||||||
|
<AdminBasicBox onPress={() => router.push(...)}>
|
||||||
|
...
|
||||||
|
</AdminBasicBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Update API** (add pagination if needed):
|
||||||
|
```typescript
|
||||||
|
export async function apiXXX({ page = "1" }: { page?: string }) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Create Route File** (`app/(application)/admin/feature/xxx.tsx`):
|
||||||
|
```typescript
|
||||||
|
import { Admin_ScreenXXX } from "@/screens/Admin/Feature/ScreenXXX";
|
||||||
|
|
||||||
|
export default function AdminXXX() {
|
||||||
|
return <Admin_ScreenXXX />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating API Endpoints
|
||||||
|
|
||||||
|
1. Add function in appropriate service file
|
||||||
|
2. Include `page` parameter for list endpoints
|
||||||
|
3. Use `apiConfig` axios instance
|
||||||
|
4. Handle errors properly
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Issues
|
||||||
|
```bash
|
||||||
|
# Clean and rebuild
|
||||||
|
rm -rf node_modules
|
||||||
|
bun install
|
||||||
|
bunx expo prebuild --clean
|
||||||
|
|
||||||
|
# iOS specific
|
||||||
|
cd ios && pod install && cd ..
|
||||||
|
|
||||||
|
# Android specific
|
||||||
|
cd android && ./gradlew clean && cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Issues
|
||||||
|
```bash
|
||||||
|
# Clear Expo cache
|
||||||
|
bunx expo start -c
|
||||||
|
|
||||||
|
# Clear Metro cache
|
||||||
|
bunx expo start --clear
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency Issues
|
||||||
|
```bash
|
||||||
|
# Reinstall dependencies
|
||||||
|
rm -rf node_modules bun.lock
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Files
|
||||||
|
|
||||||
|
- `docs/CHANGE_LOG.md` - Change log for recent updates
|
||||||
|
- `docs/COMMIT_NOTES.md` - Commit notes and guidelines
|
||||||
|
- `docs/hipmi-note.md` - Build and deployment notes
|
||||||
|
- `docs/prompt-for-qwen-code.md` - Development prompts and patterns
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Expo Documentation](https://docs.expo.dev/)
|
||||||
|
- [React Native Documentation](https://reactnative.dev/)
|
||||||
|
- [Expo Router Documentation](https://docs.expo.dev/router/introduction/)
|
||||||
|
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"expo-font",
|
"expo-font",
|
||||||
"@rnmapbox/maps",
|
|
||||||
"@react-native-firebase/app",
|
"@react-native-firebase/app",
|
||||||
[
|
[
|
||||||
"expo-notifications",
|
"expo-notifications",
|
||||||
@@ -87,6 +86,7 @@ export default {
|
|||||||
iosDisplayInForeground: true,
|
iosDisplayInForeground: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"@maplibre/maplibre-react-native",
|
||||||
],
|
],
|
||||||
|
|
||||||
experiments: {
|
experiments: {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default function TakePicture() {
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Pressable onPress={pickImage}>
|
<Pressable onPress={pickImage}>
|
||||||
<AntDesign name="folderopen" size={32} color="white" />
|
<AntDesign name="folder-open" size={32} color="white" />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export default function Application() {
|
|||||||
}
|
}
|
||||||
footerComponent={
|
footerComponent={
|
||||||
<TabSection
|
<TabSection
|
||||||
|
|
||||||
tabs={tabsHome({
|
tabs={tabsHome({
|
||||||
acceptedForumTermsAt: data?.acceptedForumTermsAt,
|
acceptedForumTermsAt: data?.acceptedForumTermsAt,
|
||||||
profileId: data?.Profile?.id,
|
profileId: data?.Profile?.id,
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import MapsView from "@/screens/Maps/MapsView";
|
|
||||||
import MapsView2 from "@/screens/Maps/MapsView2";
|
import MapsView2 from "@/screens/Maps/MapsView2";
|
||||||
import { Text, View } from "react-native";
|
|
||||||
|
|
||||||
export interface LocationItem {
|
export interface LocationItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
@@ -13,8 +11,14 @@ export interface LocationItem {
|
|||||||
export default function Maps() {
|
export default function Maps() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MapsView />
|
{/* <Stack.Screen
|
||||||
{/* <MapsView2 />, */}
|
options={{
|
||||||
|
title: "Maps",
|
||||||
|
headerLeft: () => <BackButton />,
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
{/* {Platform.OS === "ios" ? <MapsView /> : <MapsView2 />} */}
|
||||||
|
<MapsView2 />
|
||||||
{/* <View style={{ flex: 1, backgroundColor: "gray" }}><Text style={{ color: "white" }}>Map disabled</Text></View> */}
|
{/* <View style={{ flex: 1, backgroundColor: "gray" }}><Text style={{ color: "white" }}>Map disabled</Text></View> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -94,11 +94,14 @@ export default function Portofolio() {
|
|||||||
data={data}
|
data={data}
|
||||||
listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]}
|
listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]}
|
||||||
/>
|
/>
|
||||||
|
{data?.BusinessMaps && (
|
||||||
<Portofolio_BusinessLocation
|
<Portofolio_BusinessLocation
|
||||||
data={data?.BusinessMaps}
|
data={data?.BusinessMaps}
|
||||||
imageId={data?.logoId}
|
imageId={data?.logoId}
|
||||||
setOpenDrawerLocation={setOpenDrawerLocation}
|
setOpenDrawerLocation={setOpenDrawerLocation}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Portofolio_SocialMediaSection
|
<Portofolio_SocialMediaSection
|
||||||
data={data?.Portofolio_MediaSosial}
|
data={data?.Portofolio_MediaSosial}
|
||||||
/>
|
/>
|
||||||
@@ -135,10 +138,12 @@ export default function Portofolio() {
|
|||||||
closeDrawer={() => setOpenDrawerLocation(false)}
|
closeDrawer={() => setOpenDrawerLocation(false)}
|
||||||
height={"auto"}
|
height={"auto"}
|
||||||
>
|
>
|
||||||
|
{data?.BusinessMaps?.imageId && (
|
||||||
<DummyLandscapeImage
|
<DummyLandscapeImage
|
||||||
height={200}
|
height={200}
|
||||||
imageId={data?.BusinessMaps?.imageId}
|
imageId={data?.BusinessMaps?.imageId}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<Spacing />
|
<Spacing />
|
||||||
<StackCustom gap={"xs"}>
|
<StackCustom gap={"xs"}>
|
||||||
<GridTwoView
|
<GridTwoView
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function AdminForumDetailPosting() {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadData();
|
onLoadData();
|
||||||
}, [id])
|
}, [id]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLoadData = async () => {
|
const onLoadData = async () => {
|
||||||
@@ -72,6 +72,10 @@ export default function AdminForumDetailPosting() {
|
|||||||
label: "Total Report",
|
label: "Total Report",
|
||||||
value: data?.JumlahReportPosting || 0,
|
value: data?.JumlahReportPosting || 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Postingan",
|
||||||
|
value: (data && data?.diskusi) || "-",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,13 +115,6 @@ export default function AdminForumDetailPosting() {
|
|||||||
))}
|
))}
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
</BaseBox>
|
</BaseBox>
|
||||||
|
|
||||||
<BaseBox>
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<TextCustom bold>Postingan</TextCustom>
|
|
||||||
<TextCustom>{(data && data?.diskusi) || "-"}</TextCustom>
|
|
||||||
</StackCustom>
|
|
||||||
</BaseBox>
|
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
|
|
||||||
<DrawerCustom
|
<DrawerCustom
|
||||||
|
|||||||
@@ -1,91 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenForumListComment } from "@/screens/Admin/Forum/ScreenForumListComment";
|
||||||
import {
|
|
||||||
LoaderCustom,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper
|
|
||||||
} from "@/components";
|
|
||||||
import { IconOpenTo } from "@/components/_Icon/IconOpenTo";
|
|
||||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
|
||||||
import AdminTitleTable from "@/components/_ShareComponent/Admin/TableTitle";
|
|
||||||
import AdminTableValue from "@/components/_ShareComponent/Admin/TableValue";
|
|
||||||
import { apiAdminForumCommentById } from "@/service/api-admin/api-admin-forum";
|
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
import { Divider } from "react-native-paper";
|
|
||||||
|
|
||||||
export default function AdminForumListComment() {
|
export default function AdminForumListComment() {
|
||||||
const { id } = useLocalSearchParams();
|
return <Admin_ScreenForumListComment />;
|
||||||
const [listComment, setListComment] = useState<any[] | null>(null);
|
|
||||||
const [loadList, setLoadList] = useState(false);
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadComment();
|
|
||||||
}, [id])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadComment = async () => {
|
|
||||||
try {
|
|
||||||
setLoadList(true);
|
|
||||||
const response = await apiAdminForumCommentById({
|
|
||||||
id: id as string,
|
|
||||||
category: "get-all",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setListComment(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
setListComment([]);
|
|
||||||
} finally {
|
|
||||||
setLoadList(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper
|
|
||||||
headerComponent={<AdminBackButtonAntTitle title="Daftar Komentar" />}
|
|
||||||
>
|
|
||||||
<StackCustom>
|
|
||||||
<AdminTitleTable title1="Aksi" title2="Report" title3="Komentar" />
|
|
||||||
<Divider />
|
|
||||||
{loadList ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listComment) ? (
|
|
||||||
<TextCustom align="center" color="gray">
|
|
||||||
Tidak ada komentar
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
listComment?.map((item: any, index: number) => (
|
|
||||||
<AdminTableValue
|
|
||||||
key={index}
|
|
||||||
value1={
|
|
||||||
<IconOpenTo
|
|
||||||
onPress={() => {
|
|
||||||
router.push(
|
|
||||||
`/admin/forum/${item.id}/list-report-comment`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
value2={
|
|
||||||
<TextCustom truncate={1}>
|
|
||||||
{item?.countReport || 0}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
value3={
|
|
||||||
<TextCustom truncate={2}>{item?.komentar || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,262 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenForumDetailReportComment } from "@/screens/Admin/Forum/ScreenForumDetailReportComment";
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
AlertDefaultSystem,
|
|
||||||
BaseBox,
|
|
||||||
CenterCustom,
|
|
||||||
DrawerCustom,
|
|
||||||
LoaderCustom,
|
|
||||||
MenuDrawerDynamicGrid,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
import { IconDot, IconView } from "@/components/_Icon/IconComponent";
|
|
||||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
|
||||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
|
||||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
|
||||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
|
||||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
|
||||||
import { MainColor } from "@/constants/color-palet";
|
|
||||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
|
||||||
import {
|
|
||||||
apiAdminForumCommentById,
|
|
||||||
apiAdminForumDeactivateComment,
|
|
||||||
apiAdminForumListReportCommentById,
|
|
||||||
} from "@/service/api-admin/api-admin-forum";
|
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { Divider } from "react-native-paper";
|
|
||||||
import Toast from "react-native-toast-message";
|
|
||||||
|
|
||||||
export default function AdminForumReportComment() {
|
export default function AdminForumReportComment() {
|
||||||
const { id } = useLocalSearchParams();
|
return <Admin_ScreenForumDetailReportComment />;
|
||||||
const { user } = useAuth();
|
|
||||||
const [data, setData] = useState<any | null>(null);
|
|
||||||
const [listReport, setListReport] = useState<any[] | null>(null);
|
|
||||||
const [loadList, setLoadList] = useState(false);
|
|
||||||
const [openDrawer, setOpenDrawer] = useState(false);
|
|
||||||
const [openDrawerAction, setOpenDrawerAction] = useState(false);
|
|
||||||
const [selectedReport, setSelectedReport] = useState({
|
|
||||||
id: "",
|
|
||||||
username: "",
|
|
||||||
kategori: "",
|
|
||||||
keterangan: "",
|
|
||||||
deskripsi: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [id])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setLoadList(true);
|
|
||||||
const response = await apiAdminForumCommentById({
|
|
||||||
id: id as string,
|
|
||||||
category: "get-one",
|
|
||||||
});
|
|
||||||
|
|
||||||
const responseReport = await apiAdminForumListReportCommentById({
|
|
||||||
id: id as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setData(response.data);
|
|
||||||
}
|
|
||||||
if (responseReport.success) {
|
|
||||||
setListReport(responseReport.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
setData(null);
|
|
||||||
setListReport([]);
|
|
||||||
} finally {
|
|
||||||
setLoadList(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper
|
|
||||||
headerComponent={
|
|
||||||
<AdminBackButtonAntTitle
|
|
||||||
title="Report Komentar"
|
|
||||||
rightComponent={
|
|
||||||
<ActionIcon
|
|
||||||
icon={<IconDot size={16} color={MainColor.darkblue} />}
|
|
||||||
onPress={() => setOpenDrawer(true)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<BaseBox>
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={<TextCustom bold>Username</TextCustom>}
|
|
||||||
text2={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
|
||||||
/>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={<TextCustom bold>Komentar</TextCustom>}
|
|
||||||
text2={<TextCustom>{data?.komentar || "-"}</TextCustom>}
|
|
||||||
/>
|
|
||||||
</StackCustom>
|
|
||||||
</BaseBox>
|
|
||||||
|
|
||||||
<AdminComp_BoxTitle title="Daftar Report Komentar" />
|
|
||||||
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom bold align="center">
|
|
||||||
Aksi
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={<TextCustom bold>Pelapor</TextCustom>}
|
|
||||||
text3={<TextCustom bold>Kategori Report</TextCustom>}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
{loadList ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listReport) ? (
|
|
||||||
<TextCustom align="center" color="gray">
|
|
||||||
Tidak ada report
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
listReport?.map((item: any, index: number) => (
|
|
||||||
<View key={index}>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<CenterCustom>
|
|
||||||
<ActionIcon
|
|
||||||
icon={
|
|
||||||
<IconView size={ICON_SIZE_BUTTON} color="black" />
|
|
||||||
}
|
|
||||||
onPress={() => {
|
|
||||||
setOpenDrawerAction(true);
|
|
||||||
setSelectedReport({
|
|
||||||
id: item.id,
|
|
||||||
username: item.User?.username,
|
|
||||||
kategori: item.ForumMaster_KategoriReport?.title,
|
|
||||||
keterangan:
|
|
||||||
item.ForumMaster_KategoriReport?.deskripsi,
|
|
||||||
deskripsi: item.deskripsi,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CenterCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom truncate={1}>
|
|
||||||
{item?.User?.username || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text3={
|
|
||||||
<TextCustom truncate={2}>
|
|
||||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
|
|
||||||
<DrawerCustom
|
|
||||||
isVisible={openDrawer}
|
|
||||||
closeDrawer={() => setOpenDrawer(false)}
|
|
||||||
height={"auto"}
|
|
||||||
>
|
|
||||||
<MenuDrawerDynamicGrid
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
icon: <IconTrash />,
|
|
||||||
label: "Hapus Komentar",
|
|
||||||
value: "delete",
|
|
||||||
path: "",
|
|
||||||
color: MainColor.red,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onPressItem={(item) => {
|
|
||||||
AlertDefaultSystem({
|
|
||||||
title: "Hapus Komentar",
|
|
||||||
message: "Apakah Anda yakin ingin menghapus komentar ini?",
|
|
||||||
textLeft: "Batal",
|
|
||||||
textRight: "Hapus",
|
|
||||||
onPressRight: async () => {
|
|
||||||
const deleteComment = await apiAdminForumDeactivateComment({
|
|
||||||
id: id as string,
|
|
||||||
data: {
|
|
||||||
senderId: user?.id as string,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// if (!deleteComment.success) {
|
|
||||||
// Toast.show({
|
|
||||||
// type: "error",
|
|
||||||
// text1: "Komentar gagal dihapus",
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
setOpenDrawer(false);
|
|
||||||
Toast.show({
|
|
||||||
type: "success",
|
|
||||||
text1: "Komentar berhasil dihapus",
|
|
||||||
});
|
|
||||||
router.back();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DrawerCustom>
|
|
||||||
|
|
||||||
<DrawerCustom
|
|
||||||
isVisible={openDrawerAction}
|
|
||||||
closeDrawer={() => setOpenDrawerAction(false)}
|
|
||||||
height={"auto"}
|
|
||||||
>
|
|
||||||
<StackCustom>
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Pelapor</TextCustom>}
|
|
||||||
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{selectedReport?.kategori && (
|
|
||||||
<>
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Kategori Report</TextCustom>}
|
|
||||||
value={
|
|
||||||
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Keterangan</TextCustom>}
|
|
||||||
value={
|
|
||||||
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedReport?.deskripsi && (
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Deskripsi</TextCustom>}
|
|
||||||
value={
|
|
||||||
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</DrawerCustom>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,283 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenForumDetailReportPosting } from "@/screens/Admin/Forum/ScreenForumDetailReportPosting";
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
AlertDefaultSystem,
|
|
||||||
BadgeCustom,
|
|
||||||
BaseBox,
|
|
||||||
CenterCustom,
|
|
||||||
DrawerCustom,
|
|
||||||
LoaderCustom,
|
|
||||||
MenuDrawerDynamicGrid,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
import { IconDot, IconView } from "@/components/_Icon/IconComponent";
|
|
||||||
import { IconTrash } from "@/components/_Icon/IconTrash";
|
|
||||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
|
||||||
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
|
||||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
|
||||||
import { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
|
||||||
import { MainColor } from "@/constants/color-palet";
|
|
||||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
|
||||||
import {
|
|
||||||
apiAdminForumDeactivatePosting,
|
|
||||||
apiAdminForumListReportPostingById,
|
|
||||||
apiAdminForumPostingById,
|
|
||||||
} from "@/service/api-admin/api-admin-forum";
|
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { Divider } from "react-native-paper";
|
|
||||||
import Toast from "react-native-toast-message";
|
|
||||||
|
|
||||||
export default function AdminForumReportPosting() {
|
export default function AdminForumDetailReportPosting() {
|
||||||
const { user } = useAuth();
|
return <Admin_ScreenForumDetailReportPosting />;
|
||||||
const { id } = useLocalSearchParams();
|
|
||||||
const [openDrawerPage, setOpenDrawerPage] = useState(false);
|
|
||||||
const [openDrawerAction, setOpenDrawerAction] = useState(false);
|
|
||||||
|
|
||||||
const [data, setData] = useState<any | null>(null);
|
|
||||||
const [listReport, setListReport] = useState<any[] | null>(null);
|
|
||||||
const [loadListReport, setLoadListReport] = useState(false);
|
|
||||||
const [selectedReport, setSelectedReport] = useState({
|
|
||||||
id: "",
|
|
||||||
username: "",
|
|
||||||
kategori: "",
|
|
||||||
keterangan: "",
|
|
||||||
deskripsi: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [id])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setLoadListReport(true);
|
|
||||||
const response = await apiAdminForumPostingById({
|
|
||||||
id: id as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
const responseReport = await apiAdminForumListReportPostingById({
|
|
||||||
id: id as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setData(response.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseReport.success) {
|
|
||||||
setListReport(responseReport.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
} finally {
|
|
||||||
setLoadListReport(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper
|
|
||||||
headerComponent={
|
|
||||||
<AdminBackButtonAntTitle
|
|
||||||
title="Report Posting"
|
|
||||||
rightComponent={
|
|
||||||
<ActionIcon
|
|
||||||
icon={<IconDot size={16} color={MainColor.darkblue} />}
|
|
||||||
onPress={() => setOpenDrawerPage(true)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<BaseBox>
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={<TextCustom bold>Username</TextCustom>}
|
|
||||||
text2={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={<TextCustom bold>Status</TextCustom>}
|
|
||||||
text2={
|
|
||||||
data && data?.ForumMaster_StatusPosting?.status ? (
|
|
||||||
<BadgeCustom
|
|
||||||
color={
|
|
||||||
data?.ForumMaster_StatusPosting?.status === "Open"
|
|
||||||
? MainColor.green
|
|
||||||
: MainColor.red
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{data?.ForumMaster_StatusPosting?.status === "Open"
|
|
||||||
? "Open"
|
|
||||||
: "Close"}
|
|
||||||
</BadgeCustom>
|
|
||||||
) : (
|
|
||||||
<TextCustom>{"-"}</TextCustom>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={<TextCustom bold>Postingan</TextCustom>}
|
|
||||||
text2={<TextCustom>{data?.diskusi || "-"}</TextCustom>}
|
|
||||||
/>
|
|
||||||
</StackCustom>
|
|
||||||
</BaseBox>
|
|
||||||
|
|
||||||
<AdminComp_BoxTitle title="Daftar Report Posting" />
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom bold align="center">
|
|
||||||
Aksi
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={<TextCustom bold>Pelapor</TextCustom>}
|
|
||||||
text3={<TextCustom bold>Kategori Report</TextCustom>}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
{loadListReport ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listReport) ? (
|
|
||||||
<TextCustom align="center" color={"gray"}>
|
|
||||||
Belum ada report
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
listReport?.map((item: any, index: number) => (
|
|
||||||
<View key={index}>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<CenterCustom>
|
|
||||||
<ActionIcon
|
|
||||||
icon={
|
|
||||||
<IconView size={ICON_SIZE_BUTTON} color="black" />
|
|
||||||
}
|
|
||||||
onPress={() => {
|
|
||||||
setOpenDrawerAction(true);
|
|
||||||
setSelectedReport({
|
|
||||||
id: item?.id,
|
|
||||||
username: item?.User?.username,
|
|
||||||
kategori: item?.ForumMaster_KategoriReport?.title,
|
|
||||||
keterangan:
|
|
||||||
item?.ForumMaster_KategoriReport?.deskripsi,
|
|
||||||
deskripsi: item?.deskripsi,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CenterCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom truncate>
|
|
||||||
{item?.User?.username || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text3={
|
|
||||||
<TextCustom truncate={2}>
|
|
||||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
|
|
||||||
<DrawerCustom
|
|
||||||
isVisible={openDrawerPage}
|
|
||||||
closeDrawer={() => setOpenDrawerPage(false)}
|
|
||||||
height={"auto"}
|
|
||||||
>
|
|
||||||
<MenuDrawerDynamicGrid
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
icon: <IconTrash />,
|
|
||||||
label: "Hapus Posting",
|
|
||||||
value: "delete",
|
|
||||||
path: "",
|
|
||||||
color: MainColor.red,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onPressItem={(item) => {
|
|
||||||
AlertDefaultSystem({
|
|
||||||
title: "Hapus Posting",
|
|
||||||
message: "Apakah Anda yakin ingin menghapus posting ini?",
|
|
||||||
textLeft: "Batal",
|
|
||||||
textRight: "Hapus",
|
|
||||||
onPressRight: async () => {
|
|
||||||
const response = await apiAdminForumDeactivatePosting({
|
|
||||||
id: id as string,
|
|
||||||
data: {
|
|
||||||
senderId: user?.id as string,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.success) {
|
|
||||||
Toast.show({
|
|
||||||
type: "error",
|
|
||||||
text1: "Posting gagal dihapus",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpenDrawerPage(false);
|
|
||||||
Toast.show({
|
|
||||||
type: "success",
|
|
||||||
text1: "Posting berhasil dihapus",
|
|
||||||
});
|
|
||||||
router.back();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DrawerCustom>
|
|
||||||
|
|
||||||
<DrawerCustom
|
|
||||||
isVisible={openDrawerAction}
|
|
||||||
closeDrawer={() => setOpenDrawerAction(false)}
|
|
||||||
height={"auto"}
|
|
||||||
>
|
|
||||||
<StackCustom>
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Pelapor</TextCustom>}
|
|
||||||
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{selectedReport?.kategori && (
|
|
||||||
<>
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Kategori Report</TextCustom>}
|
|
||||||
value={
|
|
||||||
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Keterangan</TextCustom>}
|
|
||||||
value={
|
|
||||||
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedReport?.deskripsi && (
|
|
||||||
<GridSpan_4_8
|
|
||||||
label={<TextCustom bold>Deskripsi</TextCustom>}
|
|
||||||
value={
|
|
||||||
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</DrawerCustom>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenForumPosting } from "@/screens/Admin/Forum/ScreenForumPosting";
|
||||||
import {
|
|
||||||
ClickableCustom,
|
|
||||||
LoaderCustom,
|
|
||||||
SearchInput,
|
|
||||||
Spacing,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
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 { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
|
||||||
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
|
||||||
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";
|
|
||||||
|
|
||||||
export default function AdminForumPosting() {
|
export default function AdminForumPosting() {
|
||||||
const [list, setList] = useState<any | null>(null);
|
return <Admin_ScreenForumPosting />;
|
||||||
const [loadList, setLoadList] = useState(false);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
handlerLoadList();
|
|
||||||
}, [search])
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlerLoadList = async () => {
|
|
||||||
try {
|
|
||||||
setLoadList(true);
|
|
||||||
const response = await apiAdminForum({
|
|
||||||
category: "posting",
|
|
||||||
search: search,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("DATA", JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
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 postingan"
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
|
|
||||||
<AdminComp_BoxTitle title={"Posting"} rightComponent={rightComponent} />
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={<TextCustom bold truncate>Username</TextCustom>}
|
|
||||||
text2={<TextCustom bold truncate> Postingan</TextCustom>}
|
|
||||||
text3={<TextCustom bold align="center" truncate> Report Posting</TextCustom>}
|
|
||||||
text4={<TextCustom bold align="center" truncate> Komentar</TextCustom>}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
<Spacing />
|
|
||||||
<StackCustom>
|
|
||||||
{loadList ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(list) ? (
|
|
||||||
<TextCustom align="center" color="gray">
|
|
||||||
Belum ada data
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
list?.map((item: any, index: number) => (
|
|
||||||
<View key={index}>
|
|
||||||
<ClickableCustom
|
|
||||||
onPress={() => {
|
|
||||||
router.push(`/admin/forum/${item.id}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom truncate={1}>
|
|
||||||
{item?.Author?.username || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom truncate>
|
|
||||||
{item?.diskusi || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text3={
|
|
||||||
<TextCustom align="center" truncate={2}>
|
|
||||||
{item?.reportPosting || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text4={
|
|
||||||
<TextCustom align="center" truncate={2}>
|
|
||||||
{item?.komentar || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</ClickableCustom>
|
|
||||||
<Divider />
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,137 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenForumReportComment } from "@/screens/Admin/Forum/ScreenForumReportComment";
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
ClickableCustom,
|
|
||||||
LoaderCustom,
|
|
||||||
SearchInput,
|
|
||||||
Spacing,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
import { IconView } from "@/components/_Icon/IconComponent";
|
|
||||||
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 { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
|
||||||
import { MainColor } from "@/constants/color-palet";
|
|
||||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
|
||||||
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
|
||||||
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";
|
|
||||||
|
|
||||||
export default function AdminForumReportComment() {
|
export default function AdminForumReportComment() {
|
||||||
const [listData, setListData] = useState<any[] | null>(null);
|
return <Admin_ScreenForumReportComment />;
|
||||||
const [loadList, setLoadList] = useState<boolean>(false);
|
|
||||||
const [search, setSearch] = useState<string>("");
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [search])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setLoadList(true);
|
|
||||||
|
|
||||||
const response = await apiAdminForum({
|
|
||||||
category: "report_comment",
|
|
||||||
search: search,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setListData(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
} finally {
|
|
||||||
setLoadList(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rightComponent = (
|
|
||||||
<SearchInput
|
|
||||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
|
||||||
placeholder="Cari Komentar"
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
|
|
||||||
<AdminComp_BoxTitle
|
|
||||||
title="Report Komentar"
|
|
||||||
rightComponent={rightComponent}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom bold truncate>
|
|
||||||
Pelapor
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom bold truncate>
|
|
||||||
Komentar
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text3={
|
|
||||||
<TextCustom bold truncate>
|
|
||||||
Jenis Laporan
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
<Spacing />
|
|
||||||
<StackCustom gap={"lg"}>
|
|
||||||
{loadList ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<TextCustom align="center" color="gray">
|
|
||||||
Belum ada data
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
listData?.map((item: any, index: number) => (
|
|
||||||
<View key={index}>
|
|
||||||
<ClickableCustom
|
|
||||||
onPress={() => {
|
|
||||||
router.push(
|
|
||||||
`/admin/forum/${item?.Forum_Komentar?.id}/list-report-comment`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom truncate={1}>
|
|
||||||
{item?.User?.username || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom truncate={2}>
|
|
||||||
{item?.Forum_Komentar?.komentar || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text3={
|
|
||||||
<TextCustom truncate={2}>
|
|
||||||
{item?.ForumMaster_KategoriReport?.title || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ClickableCustom>
|
|
||||||
<Spacing />
|
|
||||||
<Divider />
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenForumReportPosting } from "@/screens/Admin/Forum/ScreenForumReportPosting";
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
ClickableCustom,
|
|
||||||
Divider,
|
|
||||||
LoaderCustom,
|
|
||||||
SearchInput,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
import { IconView } from "@/components/_Icon/IconComponent";
|
|
||||||
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 { GridSpan_NewComponent } from "@/components/_ShareComponent/GridSpan_NewComponent";
|
|
||||||
import { MainColor } from "@/constants/color-palet";
|
|
||||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
|
||||||
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
|
||||||
import { router, useFocusEffect } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
|
|
||||||
export default function AdminForumReportPosting() {
|
export default function AdminForumReportPosting() {
|
||||||
const [listData, setListData] = useState<any[] | null>(null);
|
return <Admin_ScreenForumReportPosting />;
|
||||||
const [loadList, setLoadList] = useState<boolean>(false);
|
|
||||||
const [search, setSearch] = useState<string>("");
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [search])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setLoadList(true);
|
|
||||||
|
|
||||||
const response = await apiAdminForum({
|
|
||||||
category: "report_posting",
|
|
||||||
search: search,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setListData(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
} finally {
|
|
||||||
setLoadList(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rightComponent = (
|
|
||||||
<SearchInput
|
|
||||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
|
||||||
placeholder="Cari Postingan"
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper headerComponent={<AdminTitlePage title="Forum" />}>
|
|
||||||
<AdminComp_BoxTitle
|
|
||||||
title="Report Posting"
|
|
||||||
rightComponent={rightComponent}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom bold truncate>
|
|
||||||
Pelapor
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom bold truncate>
|
|
||||||
Postingan
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
<StackCustom>
|
|
||||||
{loadList ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<TextCustom align="center" color="gray">
|
|
||||||
Belum ada data
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
listData?.map((item: any, index: number) => (
|
|
||||||
<View key={index}>
|
|
||||||
<ClickableCustom
|
|
||||||
onPress={() => {
|
|
||||||
router.push(
|
|
||||||
`/admin/forum/${item?.Forum_Posting?.id}/list-report-posting`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GridSpan_NewComponent
|
|
||||||
text1={
|
|
||||||
<TextCustom truncate={1}>
|
|
||||||
{item?.User?.username || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
text2={
|
|
||||||
<TextCustom truncate={1}>
|
|
||||||
{item?.Forum_Posting?.diskusi || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ClickableCustom>
|
|
||||||
<Divider />
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButt
|
|||||||
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
|
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
|
||||||
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
|
import AdminButtonReview from "@/components/_ShareComponent/Admin/ButtonReview";
|
||||||
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||||
|
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||||
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||||
import ReportBox from "@/components/Box/ReportBox";
|
import ReportBox from "@/components/Box/ReportBox";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
@@ -182,9 +183,9 @@ export default function AdminInvestmentDetail() {
|
|||||||
|
|
||||||
<BaseBox>
|
<BaseBox>
|
||||||
<StackCustom>
|
<StackCustom>
|
||||||
<GridSpan_4_8
|
<GridTwoView
|
||||||
label={<TextCustom bold>File Prospektus</TextCustom>}
|
leftItem={<TextCustom bold>File Prospektus</TextCustom>}
|
||||||
value={
|
rightItem={
|
||||||
<ButtonCustom
|
<ButtonCustom
|
||||||
iconLeft={
|
iconLeft={
|
||||||
<IconProspectus
|
<IconProspectus
|
||||||
@@ -202,9 +203,10 @@ export default function AdminInvestmentDetail() {
|
|||||||
</ButtonCustom>
|
</ButtonCustom>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<GridSpan_4_8
|
<GridTwoView
|
||||||
label={<TextCustom bold>File Dokumen</TextCustom>}
|
|
||||||
value={
|
leftItem={<TextCustom bold>File Dokumen</TextCustom>}
|
||||||
|
rightItem={
|
||||||
<StackCustom>
|
<StackCustom>
|
||||||
{_.isEmpty(data?.DokumenInvestasi) ? (
|
{_.isEmpty(data?.DokumenInvestasi) ? (
|
||||||
<TextCustom align="center">-</TextCustom>
|
<TextCustom align="center">-</TextCustom>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default function AdminInvestmentTransactionDetail() {
|
|||||||
value: (data && data?.MasterBank?.namaBank) || "-",
|
value: (data && data?.MasterBank?.namaBank) || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Jumlah Investasi",
|
label: "Nominal",
|
||||||
value: (data && `Rp. ${formatCurrencyDisplay(data?.nominal)}`) || "-",
|
value: (data && `Rp. ${formatCurrencyDisplay(data?.nominal)}`) || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,195 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenInvestmentListOfInvestor } from "@/screens/Admin/Investment/ScreenInvestmentListOfInvestor";
|
||||||
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 NoDataText from "@/components/_ShareComponent/NoDataText";
|
|
||||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
|
||||||
import { apiAdminInvestmentListOfInvestor } from "@/service/api-admin/api-admin-investment";
|
|
||||||
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";
|
|
||||||
|
|
||||||
export default function AdminInvestmentListOfInvestor() {
|
export default function AdminInvestmentListOfInvestor() {
|
||||||
const { id } = useLocalSearchParams();
|
return <Admin_ScreenInvestmentListOfInvestor />;
|
||||||
console.log("[ID]", id);
|
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onLoadMaster();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onLoadMaster = async () => {
|
|
||||||
try {
|
|
||||||
const response = await apiMasterTransaction();
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setMaster(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
setMaster([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
React.useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [id, selectValue])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setLoadData(true);
|
|
||||||
const response = await apiAdminInvestmentListOfInvestor({
|
|
||||||
id: id as string,
|
|
||||||
status: selectedStatus as any,
|
|
||||||
});
|
|
||||||
console.log("[LIST OF INVESTOR]", JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setListData(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
setListData([]);
|
|
||||||
} finally {
|
|
||||||
setLoadData(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onLoadMaster();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
|
|
||||||
const headerComponent = (
|
|
||||||
<StackCustom gap={"xs"}>
|
|
||||||
<AdminBackButtonAntTitle title="Daftar Investor" />
|
|
||||||
{searchComponent}
|
|
||||||
</StackCustom>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper headerComponent={headerComponent}>
|
|
||||||
<StackCustom>
|
|
||||||
<GridViewCustomSpan
|
|
||||||
span1={3}
|
|
||||||
span2={5}
|
|
||||||
span3={4}
|
|
||||||
component1={
|
|
||||||
<TextCustom bold align="center">
|
|
||||||
Aksi
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
component2={
|
|
||||||
<TextCustom bold align="center">
|
|
||||||
Investor
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
component3={
|
|
||||||
<TextCustom bold align="center">
|
|
||||||
Status
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
<StackCustom>
|
|
||||||
{loadData ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<NoDataText />
|
|
||||||
) : (
|
|
||||||
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/investment/${item?.id}/${_.lowerCase(
|
|
||||||
item?.StatusInvoice?.name
|
|
||||||
)}/transaction-detail`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CenterCustom>
|
|
||||||
}
|
|
||||||
component2={
|
|
||||||
<TextCustom bold align="center" truncate>
|
|
||||||
{item?.Author?.username || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
component3={
|
|
||||||
<BadgeCustom
|
|
||||||
style={{ alignSelf: "center" }}
|
|
||||||
color={colorBadgeTransaction({
|
|
||||||
status: item?.StatusInvoice?.name,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{item?.StatusInvoice?.name}
|
|
||||||
</BadgeCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,113 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
import { Admin_ScreenInvestmentStatus } from "@/screens/Admin/Investment/ScreenInvestmentStatus";
|
||||||
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 NoDataText from "@/components/_ShareComponent/NoDataText";
|
|
||||||
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
|
|
||||||
import { apiAdminInvestment } from "@/service/api-admin/api-admin-investment";
|
|
||||||
import { Octicons } from "@expo/vector-icons";
|
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import React, { useCallback } from "react";
|
|
||||||
import { Divider } from "react-native-paper";
|
|
||||||
|
|
||||||
export default function AdminInvestmentStatus() {
|
export default function AdminInvestmentStatus() {
|
||||||
const { status } = useLocalSearchParams();
|
return <Admin_ScreenInvestmentStatus />;
|
||||||
const [listData, setListData] = React.useState<any[] | null>(null);
|
|
||||||
const [loadData, setLoadingData] = React.useState(false);
|
|
||||||
const [search, setSearch] = React.useState("");
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [status, search])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setLoadingData(true);
|
|
||||||
const response = await apiAdminInvestment({
|
|
||||||
category: status as "publish" | "review" | "reject",
|
|
||||||
search,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
setListData(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
setListData([]);
|
|
||||||
} finally {
|
|
||||||
setLoadingData(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rightComponent = (
|
|
||||||
<SearchInput
|
|
||||||
containerStyle={{ width: "100%", marginBottom: 0 }}
|
|
||||||
placeholder="Cari"
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ViewWrapper headerComponent={<AdminTitlePage title="Investasi" />}>
|
|
||||||
<StackCustom gap={"sm"}>
|
|
||||||
<AdminComp_BoxTitle
|
|
||||||
title={`${_.startCase(status as string)}`}
|
|
||||||
rightComponent={rightComponent}
|
|
||||||
/>
|
|
||||||
<AdminTitleTable
|
|
||||||
title1="Aksi"
|
|
||||||
title2="Username"
|
|
||||||
title3="Judul Investasi"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{loadData ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<NoDataText />
|
|
||||||
) : (
|
|
||||||
listData?.map((item: any, index: number) => (
|
|
||||||
<AdminTableValue
|
|
||||||
key={index}
|
|
||||||
value1={
|
|
||||||
<ActionIcon
|
|
||||||
icon={
|
|
||||||
<Octicons
|
|
||||||
name="eye"
|
|
||||||
size={ICON_SIZE_BUTTON}
|
|
||||||
color="black"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onPress={() => {
|
|
||||||
router.push(`/admin/investment/${item.id}/${status}`);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
value2={<TextCustom truncate={1}>{item?.author?.username}</TextCustom>}
|
|
||||||
value3={
|
|
||||||
<TextCustom truncate={2}>
|
|
||||||
{item?.title}
|
|
||||||
</TextCustom>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</StackCustom>
|
|
||||||
</ViewWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
42
bun.lock
42
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"name": "hipmi-mobile",
|
"name": "hipmi-mobile",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
|
"@maplibre/maplibre-react-native": "^10.4.2",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-community/datetimepicker": "8.4.4",
|
"@react-native-community/datetimepicker": "8.4.4",
|
||||||
"@react-native-firebase/app": "^23.7.0",
|
"@react-native-firebase/app": "^23.7.0",
|
||||||
@@ -14,7 +15,6 @@
|
|||||||
"@react-navigation/elements": "^2.3.8",
|
"@react-navigation/elements": "^2.3.8",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
"@react-navigation/native-stack": "^7.3.10",
|
"@react-navigation/native-stack": "^7.3.10",
|
||||||
"@rnmapbox/maps": "^10.2.7",
|
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
@@ -580,6 +580,8 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@maplibre/maplibre-react-native": ["@maplibre/maplibre-react-native@10.4.2", "", { "dependencies": { "@turf/distance": "^7.1.0", "@turf/helpers": "^7.1.0", "@turf/length": "^7.1.0", "@turf/nearest-point-on-line": "^7.1.0", "debounce": "^2.2.0" }, "peerDependencies": { "@expo/config-plugins": ">=7", "@types/geojson": "^7946.0.0", "@types/react": ">=16.6.1", "react": ">=16.6.1", "react-native": ">=0.59.9" }, "optionalPeers": ["@expo/config-plugins", "@types/geojson", "@types/react"] }, "sha512-5qAfaEe66eMXyILklm2DMHwyaXwXxsZWVop4BqfU7AyTg13LHAcaMmLJNJ3jPkMtiJvjH2m8ywGnobdIg2I0lg=="],
|
||||||
|
|
||||||
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
"@motionone/animation": ["@motionone/animation@10.18.0", "", { "dependencies": { "@motionone/easing": "^10.18.0", "@motionone/types": "^10.17.1", "@motionone/utils": "^10.18.0", "tslib": "^2.3.1" } }, "sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw=="],
|
||||||
|
|
||||||
"@motionone/dom": ["@motionone/dom@10.12.0", "", { "dependencies": { "@motionone/animation": "^10.12.0", "@motionone/generators": "^10.12.0", "@motionone/types": "^10.12.0", "@motionone/utils": "^10.12.0", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw=="],
|
"@motionone/dom": ["@motionone/dom@10.12.0", "", { "dependencies": { "@motionone/animation": "^10.12.0", "@motionone/generators": "^10.12.0", "@motionone/types": "^10.12.0", "@motionone/utils": "^10.12.0", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw=="],
|
||||||
@@ -742,8 +744,6 @@
|
|||||||
|
|
||||||
"@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
|
"@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
|
||||||
|
|
||||||
"@rnmapbox/maps": ["@rnmapbox/maps@10.2.10", "", { "dependencies": { "@turf/along": "6.5.0", "@turf/distance": "6.5.0", "@turf/helpers": "6.5.0", "@turf/length": "6.5.0", "@turf/nearest-point-on-line": "6.5.0", "@types/geojson": "^7946.0.7", "debounce": "^2.2.0" }, "peerDependencies": { "expo": ">=47.0.0", "mapbox-gl": "^2.9.0", "react": ">=17.0.0", "react-dom": ">= 17.0.0", "react-native": ">=0.69" }, "optionalPeers": ["expo", "mapbox-gl", "react-dom"] }, "sha512-OfjW0rHp5bUWfzBo5fZ7qdKwAzGoocXYTsSssSPVMxZ2Y7axuhcbmsO5bV6gg+BJs5RwEsghzwTIoGydBNUClA=="],
|
|
||||||
|
|
||||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||||
|
|
||||||
"@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
|
"@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
|
||||||
@@ -764,29 +764,17 @@
|
|||||||
|
|
||||||
"@tsconfig/node18": ["@tsconfig/node18@18.2.6", "", {}, "sha512-eAWQzAjPj18tKnDzmWstz4OyWewLUNBm9tdoN9LayzoboRktYx3Enk1ZXPmThj55L7c4VWYq/Bzq0A51znZfhw=="],
|
"@tsconfig/node18": ["@tsconfig/node18@18.2.6", "", {}, "sha512-eAWQzAjPj18tKnDzmWstz4OyWewLUNBm9tdoN9LayzoboRktYx3Enk1ZXPmThj55L7c4VWYq/Bzq0A51znZfhw=="],
|
||||||
|
|
||||||
"@turf/along": ["@turf/along@6.5.0", "", { "dependencies": { "@turf/bearing": "^6.5.0", "@turf/destination": "^6.5.0", "@turf/distance": "^6.5.0", "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" } }, "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw=="],
|
"@turf/distance": ["@turf/distance@7.3.4", "", { "dependencies": { "@turf/helpers": "7.3.4", "@turf/invariant": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-9drWgd46uHPPyzgrcRQLgSvdS/SjVlQ6ZIBoRQagS5P2kSjUbcOXHIMeOSPwfxwlKhEtobLyr+IiR2ns1TfF8w=="],
|
||||||
|
|
||||||
"@turf/bbox": ["@turf/bbox@7.3.4", "", { "dependencies": { "@turf/helpers": "7.3.4", "@turf/meta": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-D5ErVWtfQbEPh11yzI69uxqrcJmbPU/9Y59f1uTapgwAwQHQztDWgsYpnL3ns8r1GmPWLP8sGJLVTIk2TZSiYA=="],
|
"@turf/helpers": ["@turf/helpers@7.3.4", "", { "dependencies": { "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-U/S5qyqgx3WTvg4twaH0WxF3EixoTCfDsmk98g1E3/5e2YKp7JKYZdz0vivsS5/UZLJeZDEElOSFH4pUgp+l7g=="],
|
||||||
|
|
||||||
"@turf/bearing": ["@turf/bearing@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" } }, "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A=="],
|
"@turf/invariant": ["@turf/invariant@7.3.4", "", { "dependencies": { "@turf/helpers": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-88Eo4va4rce9sNZs6XiMJowWkikM3cS2TBhaCKlU+GFHdNf8PFEpiU42VDU8q5tOF6/fu21Rvlke5odgOGW4AQ=="],
|
||||||
|
|
||||||
"@turf/destination": ["@turf/destination@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" } }, "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ=="],
|
"@turf/length": ["@turf/length@7.3.4", "", { "dependencies": { "@turf/distance": "7.3.4", "@turf/helpers": "7.3.4", "@turf/meta": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-Dg1GnQ/B2go5NIWXt91N4L7XTjIgIWCftBSYIXkrpIM7QGjItzglek0Z5caytvb8ZRWXzZOGs8//+Q5we91WuQ=="],
|
||||||
|
|
||||||
"@turf/distance": ["@turf/distance@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" } }, "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg=="],
|
"@turf/meta": ["@turf/meta@7.3.4", "", { "dependencies": { "@turf/helpers": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-tlmw9/Hs1p2n0uoHVm1w3ugw1I6L8jv9YZrcdQa4SH5FX5UY0ATrKeIvfA55FlL//PGuYppJp+eyg/0eb4goqw=="],
|
||||||
|
|
||||||
"@turf/helpers": ["@turf/helpers@6.5.0", "", {}, "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw=="],
|
"@turf/nearest-point-on-line": ["@turf/nearest-point-on-line@7.3.4", "", { "dependencies": { "@turf/distance": "7.3.4", "@turf/helpers": "7.3.4", "@turf/invariant": "7.3.4", "@turf/meta": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-DQrP3lRju83rIXFN68tUEpc7ki/eRwdwBkK2CTT4RAcyCxbcH2NGJPQv8dYiww/Ar77u1WLVn+aINXZH904dWw=="],
|
||||||
|
|
||||||
"@turf/invariant": ["@turf/invariant@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0" } }, "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg=="],
|
|
||||||
|
|
||||||
"@turf/length": ["@turf/length@6.5.0", "", { "dependencies": { "@turf/distance": "^6.5.0", "@turf/helpers": "^6.5.0", "@turf/meta": "^6.5.0" } }, "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig=="],
|
|
||||||
|
|
||||||
"@turf/line-intersect": ["@turf/line-intersect@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", "@turf/line-segment": "^6.5.0", "@turf/meta": "^6.5.0", "geojson-rbush": "3.x" } }, "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA=="],
|
|
||||||
|
|
||||||
"@turf/line-segment": ["@turf/line-segment@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", "@turf/meta": "^6.5.0" } }, "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw=="],
|
|
||||||
|
|
||||||
"@turf/meta": ["@turf/meta@6.5.0", "", { "dependencies": { "@turf/helpers": "^6.5.0" } }, "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA=="],
|
|
||||||
|
|
||||||
"@turf/nearest-point-on-line": ["@turf/nearest-point-on-line@6.5.0", "", { "dependencies": { "@turf/bearing": "^6.5.0", "@turf/destination": "^6.5.0", "@turf/distance": "^6.5.0", "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", "@turf/line-intersect": "^6.5.0", "@turf/meta": "^6.5.0" } }, "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg=="],
|
|
||||||
|
|
||||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
@@ -1482,8 +1470,6 @@
|
|||||||
|
|
||||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
"geojson-rbush": ["geojson-rbush@3.2.0", "", { "dependencies": { "@turf/bbox": "*", "@turf/helpers": "6.x", "@turf/meta": "6.x", "@types/geojson": "7946.0.8", "rbush": "^3.0.1" } }, "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w=="],
|
|
||||||
|
|
||||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
@@ -2082,14 +2068,10 @@
|
|||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="],
|
|
||||||
|
|
||||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||||
|
|
||||||
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
|
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
|
||||||
|
|
||||||
"rbush": ["rbush@3.0.1", "", { "dependencies": { "quickselect": "^2.0.0" } }, "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w=="],
|
|
||||||
|
|
||||||
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
||||||
|
|
||||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||||
@@ -2694,10 +2676,6 @@
|
|||||||
|
|
||||||
"@testing-library/react-native/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
|
"@testing-library/react-native/pretty-format": ["pretty-format@30.2.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA=="],
|
||||||
|
|
||||||
"@turf/bbox/@turf/helpers": ["@turf/helpers@7.3.4", "", { "dependencies": { "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-U/S5qyqgx3WTvg4twaH0WxF3EixoTCfDsmk98g1E3/5e2YKp7JKYZdz0vivsS5/UZLJeZDEElOSFH4pUgp+l7g=="],
|
|
||||||
|
|
||||||
"@turf/bbox/@turf/meta": ["@turf/meta@7.3.4", "", { "dependencies": { "@turf/helpers": "7.3.4", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-tlmw9/Hs1p2n0uoHVm1w3ugw1I6L8jv9YZrcdQa4SH5FX5UY0ATrKeIvfA55FlL//PGuYppJp+eyg/0eb4goqw=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
@@ -2790,8 +2768,6 @@
|
|||||||
|
|
||||||
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||||
|
|
||||||
"geojson-rbush/@types/geojson": ["@types/geojson@7946.0.8", "", {}, "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="],
|
|
||||||
|
|
||||||
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|||||||
101
docs/PODS.back
Normal file
101
docs/PODS.back
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
NOTE:
|
||||||
|
|
||||||
|
Untuk Development Selanjutnya:
|
||||||
|
Sekarang Anda bisa menjalankan:
|
||||||
|
|
||||||
|
1 # Untuk run iOS dev client
|
||||||
|
2 bun run ios
|
||||||
|
3
|
||||||
|
4 # Atau dengan Expo
|
||||||
|
5 bunx expo run:ios
|
||||||
|
|
||||||
|
Jika di masa depan terjadi error serupa, Anda bisa gunakan command ini:
|
||||||
|
|
||||||
|
1 cd ios
|
||||||
|
2 rm -rf Pods Podfile.lock
|
||||||
|
3 pod install
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||||
|
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
||||||
|
|
||||||
|
require 'json'
|
||||||
|
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
|
||||||
|
|
||||||
|
ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'
|
||||||
|
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
|
||||||
|
ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
|
||||||
|
ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
|
||||||
|
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
|
||||||
|
|
||||||
|
prepare_react_native_project!
|
||||||
|
|
||||||
|
target 'HIPMIBadungConnect' do
|
||||||
|
use_expo_modules!
|
||||||
|
|
||||||
|
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||||
|
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||||
|
else
|
||||||
|
config_command = [
|
||||||
|
'npx',
|
||||||
|
'expo-modules-autolinking',
|
||||||
|
'react-native-config',
|
||||||
|
'--json',
|
||||||
|
'--platform',
|
||||||
|
'ios'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
config = use_native_modules!(config_command)
|
||||||
|
|
||||||
|
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
||||||
|
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
||||||
|
|
||||||
|
|
||||||
|
use_react_native!(
|
||||||
|
:path => config[:reactNativePath],
|
||||||
|
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
|
||||||
|
# An absolute path to your application root.
|
||||||
|
:app_path => "#{Pod::Config.instance.installation_root}/..",
|
||||||
|
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
|
||||||
|
)
|
||||||
|
|
||||||
|
pod 'Firebase'
|
||||||
|
pod 'Firebase/Messaging'
|
||||||
|
|
||||||
|
# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-4092f82b887b5b9edb84642c2a56984d69b9a403
|
||||||
|
post_install do |installer|
|
||||||
|
# @generated begin @maplibre/maplibre-react-native:post-install - expo prebuild (DO NOT MODIFY) sync-6e76c80af0d70c0003d06822dd59b7c729fca472
|
||||||
|
$MLRN.post_install(installer)
|
||||||
|
# @generated end @maplibre/maplibre-react-native:post-install
|
||||||
|
|
||||||
|
# Fix all script phases with incorrect paths
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
target.build_phases.each do |phase|
|
||||||
|
next unless phase.respond_to?(:shell_script)
|
||||||
|
|
||||||
|
# Fix duplicated path issue
|
||||||
|
if phase.shell_script.include?('with-environment.sh')
|
||||||
|
# Remove any existing path and use proper relative path
|
||||||
|
phase.shell_script = phase.shell_script.gsub(
|
||||||
|
%r{(/.*?/node_modules/react-native)+/scripts/xcode/with-environment.sh},
|
||||||
|
'${PODS_ROOT}/../../node_modules/react-native/scripts/xcode/with-environment.sh'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Standard React Native post install
|
||||||
|
react_native_post_install(
|
||||||
|
installer,
|
||||||
|
config[:reactNativePath],
|
||||||
|
:mac_catalyst_enabled => false,
|
||||||
|
:ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
# @generated end post_installer
|
||||||
|
|
||||||
|
end
|
||||||
@@ -55,10 +55,10 @@ Component yang digunakan: components/_ShareComponent/NewWrapper.tsx
|
|||||||
|
|
||||||
<!-- START Prompt Admin Refactoring -->
|
<!-- START Prompt Admin Refactoring -->
|
||||||
<!-- Pindah kode ke Screen Component -->
|
<!-- Pindah kode ke Screen Component -->
|
||||||
File source: app/(application)/admin/donation/[id]/list-disbursement-of-funds.tsx
|
File source: app/(application)/admin/forum/[id]/list-comment.tsx
|
||||||
Folder tujuan: screens/Admin/Donation
|
Folder tujuan: screens/Admin/Forum
|
||||||
Nama file utama: ScreenDonationListDisbursementOfFunds.tsx
|
Nama file utama: ScreenForumListComment.tsx
|
||||||
Nama function utama: Admin_ScreenDonationListDisbursementOfFunds
|
Nama function utama: Admin_ScreenForumListComment
|
||||||
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
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"
|
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"
|
||||||
@@ -66,8 +66,8 @@ Analisa juga file "Nama file utama" , jika belum menggunakan NewWrapper pada fil
|
|||||||
|
|
||||||
|
|
||||||
<!-- Penerapan Pagination -->
|
<!-- Penerapan Pagination -->
|
||||||
Function fecth: apiAdminDonationDisbursementOfFundsListById
|
Function fecth: apiAdminForumCommentById
|
||||||
File function fetch: service/api-admin/api-admin-donation.ts
|
File function fetch: service/api-admin/api-admin-forum.ts
|
||||||
|
|
||||||
Terapkan pagination pada file "Nama file utama"
|
Terapkan pagination pada file "Nama file utama"
|
||||||
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
|
Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
|
||||||
@@ -80,10 +80,13 @@ Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
|
|||||||
<!-- END Prompt Admin Refactoring -->
|
<!-- END Prompt Admin Refactoring -->
|
||||||
|
|
||||||
<!-- Additional -->
|
<!-- Additional -->
|
||||||
File refrensi: screens/Admin/Voting/ScreenEventTypeOfEvent.tsx
|
File refrensi: screens/Admin/Forum/ScreenForumDetailReportPosting.tsx
|
||||||
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang hampir sama
|
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
|
Untuk refrensi tampilan Box bisa anda gunakan dari file: screens/Admin/Donation/BoxDonationListOfDonatur.tsx dan buatkan komponen yang mirip untuk list of donatur dengan nama file: BoxDonationListOfInvestor.tsx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Use Prompt Now -->
|
<!-- Use Prompt Now -->
|
||||||
Terapkan NewWrapper pada file: screens/Admin/App-Information/InformationBankSection.tsx
|
Terapkan NewWrapper pada file: screens/Admin/App-Information/InformationBankSection.tsx
|
||||||
@@ -108,9 +111,10 @@ Jika tidak ada props page maka tambahkan props page dan default page: "1" ( stri
|
|||||||
Jika butuh refrensi FlatList bisa lihat pada file components/_ShareComponent/NewWrapper.tsx
|
Jika butuh refrensi FlatList bisa lihat pada file components/_ShareComponent/NewWrapper.tsx
|
||||||
|
|
||||||
<!-- Create Box -->
|
<!-- Create Box -->
|
||||||
File Utama: screens/Admin/Donation/Admin_ScreenDonationStatus.tsx
|
File Utama: screens/Admin/Investment/ScreenInvestmentStatus.tsx
|
||||||
Folder tujuan: screens/Admin/Donation
|
Folder tujuan: screens/Admin/Investment
|
||||||
Buat box component baru pada file "File Utama" di bagian renderItem,
|
Reffrensi: screens/Admin/Donation/BoxDonationStatus.tsx
|
||||||
|
Buatkan box component baru pada file "File Utama" di bagian renderItem agar lebih rapi buat file baru dengan nama BoxInvestmentStatus.tsx
|
||||||
|
|
||||||
<!-- END Create Box -->
|
<!-- END Create Box -->
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "e70d3525c8e2819a8b34f22909815dab5c700c25a06c32388f3930f7b3627768",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "maplibre-gl-native-distribution",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/maplibre/maplibre-gl-native-distribution",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c68c970ff3ece56cfc3b36849db70167fa208beb",
|
||||||
|
"version" : "6.17.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
13
ios/Podfile
13
ios/Podfile
@@ -35,13 +35,6 @@ target 'HIPMIBadungConnect' do
|
|||||||
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
||||||
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
||||||
|
|
||||||
# @generated begin pre_installer - expo prebuild (DO NOT MODIFY) sync-c8812095000d6054b846ce74840f0ffb540c2757
|
|
||||||
pre_install do |installer|
|
|
||||||
# @generated begin @rnmapbox/maps-pre_installer - expo prebuild (DO NOT MODIFY) sync-ea4905840bf9fcea0acc62e92aa2e784f9d760f8
|
|
||||||
$RNMapboxMaps.pre_install(installer)
|
|
||||||
# @generated end @rnmapbox/maps-pre_installer
|
|
||||||
end
|
|
||||||
# @generated end pre_installer
|
|
||||||
|
|
||||||
use_react_native!(
|
use_react_native!(
|
||||||
:path => config[:reactNativePath],
|
:path => config[:reactNativePath],
|
||||||
@@ -56,9 +49,9 @@ target 'HIPMIBadungConnect' do
|
|||||||
|
|
||||||
# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-4092f82b887b5b9edb84642c2a56984d69b9a403
|
# @generated begin post_installer - expo prebuild (DO NOT MODIFY) sync-4092f82b887b5b9edb84642c2a56984d69b9a403
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
# @generated begin @rnmapbox/maps-post_installer - expo prebuild (DO NOT MODIFY) sync-c4e8f90e96f6b6c6ea9241dd7b52ab5f57f7bf36
|
# @generated begin @maplibre/maplibre-react-native:post-install - expo prebuild (DO NOT MODIFY) sync-6e76c80af0d70c0003d06822dd59b7c729fca472
|
||||||
$RNMapboxMaps.post_install(installer)
|
$MLRN.post_install(installer)
|
||||||
# @generated end @rnmapbox/maps-post_installer
|
# @generated end @maplibre/maplibre-react-native:post-install
|
||||||
|
|
||||||
# Fix all script phases with incorrect paths
|
# Fix all script phases with incorrect paths
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
|
|||||||
969
ios/Podfile.lock
969
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
|
"@maplibre/maplibre-react-native": "^10.4.2",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-community/datetimepicker": "8.4.4",
|
"@react-native-community/datetimepicker": "8.4.4",
|
||||||
"@react-native-firebase/app": "^23.7.0",
|
"@react-native-firebase/app": "^23.7.0",
|
||||||
@@ -21,7 +22,6 @@
|
|||||||
"@react-navigation/elements": "^2.3.8",
|
"@react-navigation/elements": "^2.3.8",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
"@react-navigation/native-stack": "^7.3.10",
|
"@react-navigation/native-stack": "^7.3.10",
|
||||||
"@rnmapbox/maps": "^10.2.7",
|
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function Admin_BoxDonationStatus({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<GridSpan_4_8
|
<GridSpan_4_8
|
||||||
label={<TextCustom>Target</TextCustom>}
|
label={<TextCustom>Target Dana</TextCustom>}
|
||||||
value={
|
value={
|
||||||
<TextCustom>
|
<TextCustom>
|
||||||
{item?.target
|
{item?.target
|
||||||
|
|||||||
292
screens/Admin/Forum/ScreenForumDetailReportComment.tsx
Normal file
292
screens/Admin/Forum/ScreenForumDetailReportComment.tsx
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
AlertDefaultSystem,
|
||||||
|
DrawerCustom,
|
||||||
|
MenuDrawerDynamicGrid,
|
||||||
|
StackCustom,
|
||||||
|
TextCustom,
|
||||||
|
} from "@/components";
|
||||||
|
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||||
|
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||||
|
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||||
|
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||||
|
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||||
|
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import {
|
||||||
|
apiAdminForumCommentById,
|
||||||
|
apiAdminForumDeactivateComment,
|
||||||
|
apiAdminForumListReportCommentById,
|
||||||
|
} from "@/service/api-admin/api-admin-forum";
|
||||||
|
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
|
export function Admin_ScreenForumDetailReportComment() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { id } = useLocalSearchParams();
|
||||||
|
const [openDrawerPage, setOpenDrawerPage] = useState(false);
|
||||||
|
const [openDrawerAction, setOpenDrawerAction] = useState(false);
|
||||||
|
const [data, setData] = useState<any | null>(null);
|
||||||
|
const [selectedReport, setSelectedReport] = useState({
|
||||||
|
id: "",
|
||||||
|
username: "",
|
||||||
|
kategori: "",
|
||||||
|
keterangan: "",
|
||||||
|
deskripsi: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load data komentar saat screen fokus
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
onLoadDataKomentar();
|
||||||
|
}, [id]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pagination untuk list report comment
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page) => {
|
||||||
|
const response = await apiAdminForumListReportCommentById({
|
||||||
|
id: id as string,
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
return { data: response.data };
|
||||||
|
}
|
||||||
|
return { data: [] };
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
dependencies: [id],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLoadDataKomentar = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiAdminForumCommentById({
|
||||||
|
id: id as string,
|
||||||
|
category: "get-one",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setData(response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render item untuk daftar report comment
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<AdminBasicBox
|
||||||
|
key={index}
|
||||||
|
style={{ marginHorizontal: 5, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
setOpenDrawerAction(true);
|
||||||
|
setSelectedReport({
|
||||||
|
id: item?.id,
|
||||||
|
username: item?.User?.username,
|
||||||
|
kategori: item?.ForumMaster_KategoriReport?.title,
|
||||||
|
keterangan: item?.ForumMaster_KategoriReport?.deskripsi,
|
||||||
|
deskripsi: item?.deskripsi,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Pelapor</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={1}>
|
||||||
|
{item?.User?.username || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Jenis Laporan</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={2}>
|
||||||
|
{item
|
||||||
|
? item?.ForumMaster_KategoriReport?.title
|
||||||
|
? item?.ForumMaster_KategoriReport?.title
|
||||||
|
: "Lainnya"
|
||||||
|
: "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component dengan back button dan menu
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminBackButtonAntTitle
|
||||||
|
title="Report Komentar"
|
||||||
|
rightComponent={
|
||||||
|
<ActionIcon
|
||||||
|
icon={<IconDot size={16} color={MainColor.darkblue} />}
|
||||||
|
onPress={() => setOpenDrawerPage(true)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Detail komentar component
|
||||||
|
const ListHeader = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminBasicBox>
|
||||||
|
<StackCustom gap={"sm"}>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Username</TextCustom>}
|
||||||
|
value={<TextCustom>{data?.Author?.username || "-"}</TextCustom>}
|
||||||
|
/>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Komentar</TextCustom>}
|
||||||
|
value={<TextCustom>{data?.komentar || "-"}</TextCustom>}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat komponen-komponen pagination
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
emptyMessage: "Belum ada report komentar",
|
||||||
|
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||||
|
isInitialLoad: pagination.isInitialLoad,
|
||||||
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
|
skeletonHeight: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NewWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderItem}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
ListHeaderComponent={ListHeader}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
tintColor={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Drawer untuk menu halaman (hapus komentar) */}
|
||||||
|
<DrawerCustom
|
||||||
|
isVisible={openDrawerPage}
|
||||||
|
closeDrawer={() => setOpenDrawerPage(false)}
|
||||||
|
height={"auto"}
|
||||||
|
>
|
||||||
|
<MenuDrawerDynamicGrid
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
icon: <IconTrash />,
|
||||||
|
label: "Hapus Komentar",
|
||||||
|
value: "delete",
|
||||||
|
path: "",
|
||||||
|
color: MainColor.red,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPressItem={(item) => {
|
||||||
|
AlertDefaultSystem({
|
||||||
|
title: "Hapus Komentar",
|
||||||
|
message: "Apakah Anda yakin ingin menghapus komentar ini?",
|
||||||
|
textLeft: "Batal",
|
||||||
|
textRight: "Hapus",
|
||||||
|
onPressRight: async () => {
|
||||||
|
const response = await apiAdminForumDeactivateComment({
|
||||||
|
id: id as string,
|
||||||
|
data: {
|
||||||
|
senderId: user?.id as string,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Komentar gagal dihapus",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenDrawerPage(false);
|
||||||
|
Toast.show({
|
||||||
|
type: "success",
|
||||||
|
text1: "Komentar berhasil dihapus",
|
||||||
|
});
|
||||||
|
router.back();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DrawerCustom>
|
||||||
|
|
||||||
|
{/* Drawer untuk detail report comment */}
|
||||||
|
<DrawerCustom
|
||||||
|
isVisible={openDrawerAction}
|
||||||
|
closeDrawer={() => setOpenDrawerAction(false)}
|
||||||
|
height={"auto"}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Pelapor</TextCustom>}
|
||||||
|
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedReport?.kategori && (
|
||||||
|
<>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Kategori Report</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Keterangan</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedReport?.deskripsi && (
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Deskripsi</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StackCustom>
|
||||||
|
</DrawerCustom>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
297
screens/Admin/Forum/ScreenForumDetailReportPosting.tsx
Normal file
297
screens/Admin/Forum/ScreenForumDetailReportPosting.tsx
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
AlertDefaultSystem,
|
||||||
|
DrawerCustom,
|
||||||
|
MenuDrawerDynamicGrid,
|
||||||
|
StackCustom,
|
||||||
|
TextCustom,
|
||||||
|
} from "@/components";
|
||||||
|
import { IconDot } from "@/components/_Icon/IconComponent";
|
||||||
|
import { IconTrash } from "@/components/_Icon/IconTrash";
|
||||||
|
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||||
|
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||||
|
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||||
|
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import {
|
||||||
|
apiAdminForumDeactivatePosting,
|
||||||
|
apiAdminForumListReportPostingById,
|
||||||
|
apiAdminForumPostingById,
|
||||||
|
} from "@/service/api-admin/api-admin-forum";
|
||||||
|
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
|
export function Admin_ScreenForumDetailReportPosting() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { id } = useLocalSearchParams();
|
||||||
|
const [openDrawerPage, setOpenDrawerPage] = useState(false);
|
||||||
|
const [openDrawerAction, setOpenDrawerAction] = useState(false);
|
||||||
|
const [data, setData] = useState<any | null>(null);
|
||||||
|
const [selectedReport, setSelectedReport] = useState({
|
||||||
|
id: "",
|
||||||
|
username: "",
|
||||||
|
kategori: "",
|
||||||
|
keterangan: "",
|
||||||
|
deskripsi: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load data postingan saat screen fokus
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
onLoadDataPosting();
|
||||||
|
}, [id]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pagination untuk list report
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page) => {
|
||||||
|
const response = await apiAdminForumListReportPostingById({
|
||||||
|
id: id as string,
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
return { data: response.data };
|
||||||
|
}
|
||||||
|
return { data: [] };
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
dependencies: [id],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLoadDataPosting = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiAdminForumPostingById({
|
||||||
|
id: id as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setData(response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render item untuk daftar report
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<AdminBasicBox
|
||||||
|
key={index}
|
||||||
|
style={{ marginHorizontal: 5, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
setOpenDrawerAction(true);
|
||||||
|
setSelectedReport({
|
||||||
|
id: item?.id,
|
||||||
|
username: item?.User?.username,
|
||||||
|
kategori: item?.ForumMaster_KategoriReport?.title,
|
||||||
|
keterangan: item?.ForumMaster_KategoriReport?.deskripsi,
|
||||||
|
deskripsi: item?.deskripsi,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Pelapor</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={1}>
|
||||||
|
{item?.User?.username || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Jenis Laporan</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={2}>
|
||||||
|
{item
|
||||||
|
? item?.ForumMaster_KategoriReport?.title
|
||||||
|
? item?.ForumMaster_KategoriReport?.title
|
||||||
|
: "Lainnya"
|
||||||
|
: "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component dengan detail postingan
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminBackButtonAntTitle
|
||||||
|
title="Detail Report Posting"
|
||||||
|
rightComponent={
|
||||||
|
<ActionIcon
|
||||||
|
icon={<IconDot size={16} color={MainColor.darkblue} />}
|
||||||
|
onPress={() => setOpenDrawerPage(true)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Detail postingan component
|
||||||
|
const ListHeader = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminBasicBox>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom bold>Username</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom>{data ? data?.Author?.username : "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom bold>Postingan</TextCustom>}
|
||||||
|
rightItem={<TextCustom>{data ? data?.diskusi : "-"}</TextCustom>}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat komponen-komponen pagination
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
emptyMessage: "Belum ada report",
|
||||||
|
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||||
|
isInitialLoad: pagination.isInitialLoad,
|
||||||
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
|
skeletonHeight: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NewWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderItem}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
ListHeaderComponent={ListHeader}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
tintColor={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Drawer untuk menu halaman (hapus posting) */}
|
||||||
|
<DrawerCustom
|
||||||
|
isVisible={openDrawerPage}
|
||||||
|
closeDrawer={() => setOpenDrawerPage(false)}
|
||||||
|
height={"auto"}
|
||||||
|
>
|
||||||
|
<MenuDrawerDynamicGrid
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
icon: <IconTrash />,
|
||||||
|
label: "Hapus Posting",
|
||||||
|
value: "delete",
|
||||||
|
path: "",
|
||||||
|
color: MainColor.red,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPressItem={(item) => {
|
||||||
|
AlertDefaultSystem({
|
||||||
|
title: "Hapus Posting",
|
||||||
|
message: "Apakah Anda yakin ingin menghapus posting ini?",
|
||||||
|
textLeft: "Batal",
|
||||||
|
textRight: "Hapus",
|
||||||
|
onPressRight: async () => {
|
||||||
|
const response = await apiAdminForumDeactivatePosting({
|
||||||
|
id: id as string,
|
||||||
|
data: {
|
||||||
|
senderId: user?.id as string,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Posting gagal dihapus",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenDrawerPage(false);
|
||||||
|
Toast.show({
|
||||||
|
type: "success",
|
||||||
|
text1: "Posting berhasil dihapus",
|
||||||
|
});
|
||||||
|
router.back();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DrawerCustom>
|
||||||
|
|
||||||
|
<DrawerCustom
|
||||||
|
isVisible={openDrawerAction}
|
||||||
|
closeDrawer={() => setOpenDrawerAction(false)}
|
||||||
|
height={"auto"}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Pelapor</TextCustom>}
|
||||||
|
value={<TextCustom>{selectedReport?.username || "-"}</TextCustom>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedReport?.kategori && (
|
||||||
|
<>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Kategori Report</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>{selectedReport?.kategori || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Keterangan</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>{selectedReport?.keterangan || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedReport?.deskripsi && (
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom bold>Deskripsi</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>{selectedReport?.deskripsi || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StackCustom>
|
||||||
|
</DrawerCustom>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
screens/Admin/Forum/ScreenForumListComment.tsx
Normal file
105
screens/Admin/Forum/ScreenForumListComment.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { StackCustom, TextCustom } from "@/components";
|
||||||
|
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||||
|
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { apiAdminForumCommentById } from "@/service/api-admin/api-admin-forum";
|
||||||
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
import { Divider } from "react-native-paper";
|
||||||
|
|
||||||
|
export function Admin_ScreenForumListComment() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { id } = useLocalSearchParams();
|
||||||
|
const [openDrawerAction, setOpenDrawerAction] = useState(false);
|
||||||
|
const [selectedComment, setSelectedComment] = useState({
|
||||||
|
id: "",
|
||||||
|
komentar: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pagination untuk list comment
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page) => {
|
||||||
|
const response = await apiAdminForumCommentById({
|
||||||
|
id: id as string,
|
||||||
|
category: "get-all",
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
return { data: response.data };
|
||||||
|
}
|
||||||
|
return { data: [] };
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
dependencies: [id],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render item untuk daftar komentar
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<AdminBasicBox
|
||||||
|
key={index}
|
||||||
|
style={{ marginHorizontal: 5, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/admin/forum/${item.id}/list-report-comment`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={"md"}>
|
||||||
|
<TextCustom truncate={1}>
|
||||||
|
Report : {item?.countReport || 0}
|
||||||
|
</TextCustom>
|
||||||
|
<Divider />
|
||||||
|
<TextCustom truncate={2}>{item?.komentar || "-"}</TextCustom>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component dengan back button
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => <AdminBackButtonAntTitle title="Daftar Komentar" />,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat komponen-komponen pagination
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
emptyMessage: "Belum ada komentar",
|
||||||
|
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||||
|
isInitialLoad: pagination.isInitialLoad,
|
||||||
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
|
skeletonHeight: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NewWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderItem}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
tintColor={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
133
screens/Admin/Forum/ScreenForumPosting.tsx
Normal file
133
screens/Admin/Forum/ScreenForumPosting.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import {
|
||||||
|
SearchInput, StackCustom,
|
||||||
|
TextCustom
|
||||||
|
} from "@/components";
|
||||||
|
import AdminBasicBox from "@/components/_ShareComponent/Admin/AdminBasicBox";
|
||||||
|
import AdminComp_BoxTitle from "@/components/_ShareComponent/Admin/BoxTitlePage";
|
||||||
|
import { GridSpan_4_8 } from "@/components/_ShareComponent/GridSpan_4_8";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl, View } from "react-native";
|
||||||
|
import { Divider } from "react-native-paper";
|
||||||
|
|
||||||
|
export function Admin_ScreenForumPosting() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
// Gunakan hook pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page, searchQuery) => {
|
||||||
|
const response = await apiAdminForum({
|
||||||
|
category: "posting",
|
||||||
|
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
|
||||||
|
const searchComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<SearchInput
|
||||||
|
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||||
|
placeholder="Cari postingan"
|
||||||
|
value={search}
|
||||||
|
onChangeText={setSearch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[search],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminComp_BoxTitle
|
||||||
|
title={"Forum Posting"}
|
||||||
|
rightComponent={searchComponent}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[searchComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render item untuk daftar posting
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<AdminBasicBox
|
||||||
|
style={{ marginHorizontal: 5, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/admin/forum/${item.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<View style={{ paddingBlock: 8 }}>
|
||||||
|
<TextCustom size={"large"} bold truncate={2}>
|
||||||
|
{item?.diskusi || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
</View>
|
||||||
|
<Divider />
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom>Komentar</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom truncate={1}>{item?.komentar || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom>Report</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom truncate={1}>{item?.reportPosting || "-"}</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat komponen-komponen pagination
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
searchQuery: search,
|
||||||
|
emptyMessage: "Belum ada data posting",
|
||||||
|
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={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
140
screens/Admin/Forum/ScreenForumReportComment.tsx
Normal file
140
screens/Admin/Forum/ScreenForumReportComment.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { SearchInput, StackCustom, TextCustom } from "@/components";
|
||||||
|
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 { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
||||||
|
import { router, useFocusEffect } from "expo-router";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
|
||||||
|
export function Admin_ScreenForumReportComment() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
// Gunakan hook pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page, searchQuery) => {
|
||||||
|
const response = await apiAdminForum({
|
||||||
|
category: "report_comment",
|
||||||
|
search: searchQuery || "",
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
return { data: response.data };
|
||||||
|
} else {
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
searchQuery: search,
|
||||||
|
dependencies: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
pagination.onRefresh();
|
||||||
|
}, []),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Komponen search input
|
||||||
|
const searchComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<SearchInput
|
||||||
|
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||||
|
placeholder="Cari Komentar"
|
||||||
|
value={search}
|
||||||
|
onChangeText={setSearch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[search],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component dengan box title
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminComp_BoxTitle
|
||||||
|
title="Report Komentar"
|
||||||
|
rightComponent={searchComponent}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[searchComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render item untuk daftar report comment
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<AdminBasicBox
|
||||||
|
key={index}
|
||||||
|
style={{ marginHorizontal: 5, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(
|
||||||
|
`/admin/forum/${item?.Forum_Komentar?.id}/list-report-comment`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Jumlah Report</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={2}>
|
||||||
|
{item?.count || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Komentar</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={2}>
|
||||||
|
{item?.Forum_Komentar?.komentar || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat komponen-komponen pagination
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
searchQuery: search,
|
||||||
|
emptyMessage: "Belum ada data report komentar",
|
||||||
|
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"]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
141
screens/Admin/Forum/ScreenForumReportPosting.tsx
Normal file
141
screens/Admin/Forum/ScreenForumReportPosting.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { SearchInput, StackCustom, TextCustom } from "@/components";
|
||||||
|
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 { MainColor } from "@/constants/color-palet";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { apiAdminForum } from "@/service/api-admin/api-admin-forum";
|
||||||
|
import { router, useFocusEffect } from "expo-router";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
|
||||||
|
export function Admin_ScreenForumReportPosting() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
// Gunakan hook pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page, searchQuery) => {
|
||||||
|
const response = await apiAdminForum({
|
||||||
|
category: "report_posting",
|
||||||
|
search: searchQuery || "",
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
|
||||||
|
return { data: response.data };
|
||||||
|
} else {
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
searchQuery: search,
|
||||||
|
dependencies: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
pagination.onRefresh();
|
||||||
|
}, []),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Komponen search input
|
||||||
|
const searchComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<SearchInput
|
||||||
|
containerStyle={{ width: "100%", marginBottom: 0 }}
|
||||||
|
placeholder="Cari Postingan"
|
||||||
|
value={search}
|
||||||
|
onChangeText={setSearch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[search],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Box title component
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminComp_BoxTitle
|
||||||
|
title="Report Posting"
|
||||||
|
rightComponent={searchComponent}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[searchComponent],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render item untuk daftar report posting
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<AdminBasicBox
|
||||||
|
key={index}
|
||||||
|
style={{ marginHorizontal: 5, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(
|
||||||
|
`/admin/forum/${item?.Forum_Posting?.id}/list-report-posting`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Jumlah Report</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={1}>
|
||||||
|
{item?.count|| "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={5}
|
||||||
|
spanRight={7}
|
||||||
|
leftItem={<TextCustom>Postingan</TextCustom>}
|
||||||
|
rightItem={
|
||||||
|
<TextCustom truncate={2}>
|
||||||
|
{item?.Forum_Posting?.diskusi || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat komponen-komponen pagination
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
searchQuery: search,
|
||||||
|
emptyMessage: "Belum ada data report posting",
|
||||||
|
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={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
screens/Admin/Investment/BoxInvestmentListOfInvestor.tsx
Normal file
63
screens/Admin/Investment/BoxInvestmentListOfInvestor.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
interface BoxInvestmentListOfInvestorProps {
|
||||||
|
item: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Admin_BoxInvestmentListOfInvestor({
|
||||||
|
item,
|
||||||
|
}: BoxInvestmentListOfInvestorProps) {
|
||||||
|
const statusName = item?.StatusInvoice?.name || "-";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AdminBasicBox
|
||||||
|
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(
|
||||||
|
`/admin/investment/${item?.id}/${_.lowerCase(
|
||||||
|
item?.StatusInvoice?.name,
|
||||||
|
)}/transaction-detail`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StackCustom gap={0}>
|
||||||
|
<StackCustom style={{ paddingBlock: 8 }}>
|
||||||
|
<TextCustom size="large" bold truncate>
|
||||||
|
{item?.Author?.username || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
</StackCustom>
|
||||||
|
<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/Investment/BoxInvestmentStatus.tsx
Normal file
54
screens/Admin/Investment/BoxInvestmentStatus.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 BoxInvestmentStatusProps {
|
||||||
|
item: any;
|
||||||
|
status?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Admin_BoxInvestmentStatus({
|
||||||
|
item,
|
||||||
|
status,
|
||||||
|
}: BoxInvestmentStatusProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AdminBasicBox
|
||||||
|
style={{ marginHorizontal: 10, marginVertical: 5 }}
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/admin/investment/${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?.MasterPencarianInvestor?.name || "-"} hari
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<GridSpan_4_8
|
||||||
|
label={<TextCustom>Target Dana</TextCustom>}
|
||||||
|
value={
|
||||||
|
<TextCustom>
|
||||||
|
{item?.targetDana
|
||||||
|
? `Rp ${formatCurrencyDisplay(item?.targetDana)}`
|
||||||
|
: "-"}
|
||||||
|
</TextCustom>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackCustom>
|
||||||
|
</AdminBasicBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
132
screens/Admin/Investment/ScreenInvestmentListOfInvestor.tsx
Normal file
132
screens/Admin/Investment/ScreenInvestmentListOfInvestor.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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 { apiAdminInvestmentListOfInvestor } from "@/service/api-admin/api-admin-investment";
|
||||||
|
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 } from "react-native";
|
||||||
|
import Admin_BoxInvestmentListOfInvestor from "./BoxInvestmentListOfInvestor";
|
||||||
|
|
||||||
|
export function Admin_ScreenInvestmentListOfInvestor() {
|
||||||
|
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 apiAdminInvestmentListOfInvestor({
|
||||||
|
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(
|
||||||
|
() => (
|
||||||
|
<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
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[master, selectValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component dengan back button dan select filter
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => <AdminBackButtonAntTitle newComponent={searchComponent} />,
|
||||||
|
[searchComponent]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render item untuk daftar investor
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<Admin_BoxInvestmentListOfInvestor 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 investor",
|
||||||
|
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"]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
screens/Admin/Investment/ScreenInvestmentStatus.tsx
Normal file
109
screens/Admin/Investment/ScreenInvestmentStatus.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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 { apiAdminInvestment } from "@/service/api-admin/api-admin-investment";
|
||||||
|
import { useLocalSearchParams } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
import Admin_BoxInvestmentStatus from "./BoxInvestmentStatus";
|
||||||
|
|
||||||
|
export function Admin_ScreenInvestmentStatus() {
|
||||||
|
const { status } = useLocalSearchParams();
|
||||||
|
const [search, setSearch] = useState<string>("");
|
||||||
|
|
||||||
|
// Gunakan hook pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page, searchQuery) => {
|
||||||
|
const response = await apiAdminInvestment({
|
||||||
|
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 investasi"
|
||||||
|
value={search}
|
||||||
|
onChangeText={(value) => setSearch(value)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[search],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render item untuk daftar investasi menggunakan Box Component
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: { item: any; index: number }) => (
|
||||||
|
<Admin_BoxInvestmentStatus
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
status={status as string}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[status],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Header component dengan judul status investasi
|
||||||
|
const headerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<AdminComp_BoxTitle
|
||||||
|
title={`Investasi ${_.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"]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -72,16 +72,16 @@ const adminListMenu: NavbarItem[] = [
|
|||||||
{ label: "Report Komentar", link: "/admin/forum/report-comment" },
|
{ label: "Report Komentar", link: "/admin/forum/report-comment" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: "Collaboration",
|
// label: "Collaboration",
|
||||||
icon: "people",
|
// icon: "people",
|
||||||
links: [
|
// links: [
|
||||||
{ label: "Dashboard", link: "/admin/collaboration" },
|
// { label: "Dashboard", link: "/admin/collaboration" },
|
||||||
{ label: "Publish", link: "/admin/collaboration/publish" },
|
// { label: "Publish", link: "/admin/collaboration/publish" },
|
||||||
{ label: "Group", link: "/admin/collaboration/group" },
|
// { label: "Group", link: "/admin/collaboration/group" },
|
||||||
{ label: "Reject", link: "/admin/collaboration/reject" },
|
// { label: "Reject", link: "/admin/collaboration/reject" },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{ label: "Maps", icon: "map", link: "/admin/maps" },
|
{ label: "Maps", icon: "map", link: "/admin/maps" },
|
||||||
{
|
{
|
||||||
label: "App Information",
|
label: "App Information",
|
||||||
@@ -165,16 +165,16 @@ const superAdminListMenu: NavbarItem[] = [
|
|||||||
{ label: "Report Komentar", link: "/admin/forum/report-comment" },
|
{ label: "Report Komentar", link: "/admin/forum/report-comment" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: "Collaboration",
|
// label: "Collaboration",
|
||||||
icon: "people",
|
// icon: "people",
|
||||||
links: [
|
// links: [
|
||||||
{ label: "Dashboard", link: "/admin/collaboration" },
|
// { label: "Dashboard", link: "/admin/collaboration" },
|
||||||
{ label: "Publish", link: "/admin/collaboration/publish" },
|
// { label: "Publish", link: "/admin/collaboration/publish" },
|
||||||
{ label: "Group", link: "/admin/collaboration/group" },
|
// { label: "Group", link: "/admin/collaboration/group" },
|
||||||
{ label: "Reject", link: "/admin/collaboration/reject" },
|
// { label: "Reject", link: "/admin/collaboration/reject" },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{ label: "Maps", icon: "map", link: "/admin/maps" },
|
{ label: "Maps", icon: "map", link: "/admin/maps" },
|
||||||
{
|
{
|
||||||
label: "App Information",
|
label: "App Information",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Icon from "react-native-vector-icons/FontAwesome";
|
|||||||
import { stylesHome } from "./homeViewStyle";
|
import { stylesHome } from "./homeViewStyle";
|
||||||
import { router, useFocusEffect } from "expo-router";
|
import { router, useFocusEffect } from "expo-router";
|
||||||
import { apiJobGetAll } from "@/service/api-client/api-job";
|
import { apiJobGetAll } from "@/service/api-client/api-job";
|
||||||
|
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
|
||||||
|
|
||||||
export default function Home_BottomFeatureSection() {
|
export default function Home_BottomFeatureSection() {
|
||||||
const [listData, setListData] = useState<any>([]);
|
const [listData, setListData] = useState<any>([]);
|
||||||
@@ -35,6 +36,10 @@ export default function Home_BottomFeatureSection() {
|
|||||||
}, [])
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!listData || listData.length === 0) {
|
||||||
|
return <CustomSkeleton height={200}/>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ClickableCustom onPress={() => router.push("/job")}>
|
<ClickableCustom onPress={() => router.push("/job")}>
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ export const tabsHome: any = ({
|
|||||||
activeIcon: "map",
|
activeIcon: "map",
|
||||||
label: "Maps",
|
label: "Maps",
|
||||||
path: "/maps",
|
path: "/maps",
|
||||||
isActive: Platform.OS === "ios" ? true : false,
|
// isActive: Platform.OS === "ios" ? true : false,
|
||||||
disabled: Platform.OS === "ios" ? false : true,
|
// disabled: Platform.OS === "ios" ? false : true,
|
||||||
|
isActive: true,
|
||||||
|
disabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "profile",
|
id: "profile",
|
||||||
|
|||||||
125
screens/Maps/DrawerMaps.tsx
Normal file
125
screens/Maps/DrawerMaps.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import {
|
||||||
|
DrawerCustom,
|
||||||
|
DummyLandscapeImage,
|
||||||
|
Spacing,
|
||||||
|
StackCustom,
|
||||||
|
TextCustom,
|
||||||
|
Grid,
|
||||||
|
ButtonCustom,
|
||||||
|
} from "@/components";
|
||||||
|
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
|
||||||
|
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||||
|
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
|
||||||
|
import { FontAwesome, Ionicons } from "@expo/vector-icons";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
|
||||||
|
interface TypeDrawerMaps {
|
||||||
|
openDrawer: boolean;
|
||||||
|
setOpenDrawer: (value: boolean) => void;
|
||||||
|
selected: {
|
||||||
|
id: string;
|
||||||
|
bidangBisnis: string;
|
||||||
|
nomorTelepon: string;
|
||||||
|
alamatBisnis: string;
|
||||||
|
namePin: string;
|
||||||
|
imageId: string;
|
||||||
|
portofolioId: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DrawerMaps({
|
||||||
|
openDrawer,
|
||||||
|
setOpenDrawer,
|
||||||
|
selected,
|
||||||
|
}: TypeDrawerMaps) {
|
||||||
|
return (
|
||||||
|
<DrawerCustom
|
||||||
|
isVisible={openDrawer}
|
||||||
|
closeDrawer={() => setOpenDrawer(false)}
|
||||||
|
height={"auto"}
|
||||||
|
>
|
||||||
|
<DummyLandscapeImage height={200} imageId={selected.imageId} />
|
||||||
|
<Spacing />
|
||||||
|
<StackCustom gap={"xs"}>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={2}
|
||||||
|
spanRight={10}
|
||||||
|
leftItem={
|
||||||
|
<FontAwesome
|
||||||
|
name="building-o"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rightItem={<TextCustom>{selected.namePin}</TextCustom>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={2}
|
||||||
|
spanRight={10}
|
||||||
|
leftItem={
|
||||||
|
<Ionicons
|
||||||
|
name="list-outline"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rightItem={<TextCustom>{selected.bidangBisnis}</TextCustom>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={2}
|
||||||
|
spanRight={10}
|
||||||
|
leftItem={
|
||||||
|
<Ionicons
|
||||||
|
name="call-outline"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rightItem={<TextCustom>{selected.nomorTelepon}</TextCustom>}
|
||||||
|
/>
|
||||||
|
<GridTwoView
|
||||||
|
spanLeft={2}
|
||||||
|
spanRight={10}
|
||||||
|
leftItem={
|
||||||
|
<Ionicons
|
||||||
|
name="location-outline"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rightItem={<TextCustom>{selected.alamatBisnis}</TextCustom>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={6} style={{ paddingRight: 10 }}>
|
||||||
|
<ButtonCustom
|
||||||
|
onPress={() => {
|
||||||
|
setOpenDrawer(false);
|
||||||
|
router.push(`/portofolio/${selected.portofolioId}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</ButtonCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
|
||||||
|
<ButtonCustom
|
||||||
|
onPress={() => {
|
||||||
|
openInDeviceMaps({
|
||||||
|
latitude: selected.latitude,
|
||||||
|
longitude: selected.longitude,
|
||||||
|
title: selected.namePin,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Buka Maps
|
||||||
|
</ButtonCustom>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</StackCustom>
|
||||||
|
</DrawerCustom>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,28 +1,166 @@
|
|||||||
import { TextCustom, ViewWrapper } from "@/components";
|
import { useCallback, useState } from "react";
|
||||||
import Mapbox from "@rnmapbox/maps";
|
import { Image, StyleSheet, View } from "react-native";
|
||||||
import { View } from "react-native";
|
|
||||||
|
|
||||||
// Nonaktifkan telemetry (opsional, untuk privasi)
|
// Cek versi >= 10.x gunakan ini
|
||||||
Mapbox.setTelemetryEnabled(false);
|
import API_IMAGE from "@/constants/api-storage";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { apiMapsGetAll } from "@/service/api-client/api-maps";
|
||||||
|
import {
|
||||||
|
Camera,
|
||||||
|
MapView,
|
||||||
|
PointAnnotation,
|
||||||
|
} from "@maplibre/maplibre-react-native";
|
||||||
|
import { useFocusEffect } from "expo-router";
|
||||||
|
import DrawerMaps from "./DrawerMaps";
|
||||||
|
|
||||||
// Gunakan style OSM gratis
|
const MAP_STYLE = "https://tiles.openfreemap.org/styles/liberty";
|
||||||
const MAP_STYLE_URL = "https://tiles.stadiamaps.com/styles/osm_bright.json";
|
|
||||||
|
interface TypeMaps {
|
||||||
|
id: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
namePin: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
authorId: string;
|
||||||
|
portofolioId: string;
|
||||||
|
imageId: string;
|
||||||
|
pinId: string | null;
|
||||||
|
Portofolio: {
|
||||||
|
id: string;
|
||||||
|
namaBisnis: string;
|
||||||
|
logoId: string;
|
||||||
|
alamatKantor: string;
|
||||||
|
tlpn: string;
|
||||||
|
MasterBidangBisnis: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultRegion = {
|
||||||
|
latitude: -8.737109,
|
||||||
|
longitude: 115.1756897,
|
||||||
|
latitudeDelta: 0.1,
|
||||||
|
longitudeDelta: 0.1,
|
||||||
|
height: 300,
|
||||||
|
};
|
||||||
|
|
||||||
// Atau gunakan MapLibre default:
|
|
||||||
// const MAP_STYLE_URL = 'https://demotiles.maplibre.org/style.json';
|
|
||||||
export default function MapsView2() {
|
export default function MapsView2() {
|
||||||
|
const [list, setList] = useState<TypeMaps[] | null>(null);
|
||||||
|
const [loadList, setLoadList] = useState(false);
|
||||||
|
const [openDrawer, setOpenDrawer] = useState(false);
|
||||||
|
const [selected, setSelected] = useState({
|
||||||
|
id: "",
|
||||||
|
bidangBisnis: "",
|
||||||
|
nomorTelepon: "",
|
||||||
|
alamatBisnis: "",
|
||||||
|
namePin: "",
|
||||||
|
imageId: "",
|
||||||
|
portofolioId: "",
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
handlerLoadList();
|
||||||
|
}, []),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlerLoadList = async () => {
|
||||||
|
try {
|
||||||
|
setLoadList(true);
|
||||||
|
const response = await apiMapsGetAll();
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// console.log("[RESPONSE]", JSON.stringify(response.data, null, 2));
|
||||||
|
setList(response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setLoadList(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewWrapper>
|
<View style={styles.container}>
|
||||||
<View style={{ flex: 1 }}>
|
<MapView style={styles.map} mapStyle={MAP_STYLE}>
|
||||||
<Mapbox.MapView style={{ flex: 1 }}>
|
<Camera
|
||||||
<Mapbox.Camera
|
|
||||||
centerCoordinate={[115.2126, -8.65]} // Bali
|
|
||||||
zoomLevel={12}
|
zoomLevel={12}
|
||||||
|
centerCoordinate={[defaultRegion.longitude, defaultRegion.latitude]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{list?.map((item: TypeMaps) => {
|
||||||
|
const imageUrl = API_IMAGE.GET({ fileId: item.Portofolio.logoId });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PointAnnotation
|
||||||
|
key={item.id}
|
||||||
|
id={item.id}
|
||||||
|
coordinate={[item.longitude, item.latitude] as [number, number]}
|
||||||
|
onSelected={() => {
|
||||||
|
setOpenDrawer(true);
|
||||||
|
setSelected({
|
||||||
|
id: item?.id,
|
||||||
|
bidangBisnis: item?.Portofolio?.MasterBidangBisnis?.name,
|
||||||
|
nomorTelepon: item?.Portofolio?.tlpn,
|
||||||
|
alamatBisnis: item?.Portofolio?.alamatKantor,
|
||||||
|
namePin: item?.namePin,
|
||||||
|
imageId: item?.imageId,
|
||||||
|
portofolioId: item?.Portofolio?.id,
|
||||||
|
latitude: item?.latitude,
|
||||||
|
longitude: item?.longitude,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={styles.markerContainer}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: imageUrl }}
|
||||||
|
style={styles.markerImage}
|
||||||
|
resizeMode="cover"
|
||||||
|
onError={(e: any) =>
|
||||||
|
console.log("Image error:", e.nativeEvent.error)
|
||||||
|
} // Tangkap error image
|
||||||
/>
|
/>
|
||||||
</Mapbox.MapView>
|
|
||||||
</View>
|
</View>
|
||||||
</ViewWrapper>
|
</PointAnnotation>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MapView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<DrawerMaps
|
||||||
|
openDrawer={openDrawer}
|
||||||
|
setOpenDrawer={setOpenDrawer}
|
||||||
|
selected={selected}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1 },
|
||||||
|
map: { flex: 1 },
|
||||||
|
markerContainer: {
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
borderRadius: 100,
|
||||||
|
overflow: "hidden", // Wajib agar borderRadius terapply pada Image
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: MainColor.darkblue, // Opsional, biar lebih cantik
|
||||||
|
elevation: 4, // Shadow Android
|
||||||
|
shadowColor: "#000", // Shadow iOS
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.3,
|
||||||
|
shadowRadius: 3,
|
||||||
|
},
|
||||||
|
markerImage: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { apiConfig } from "../api-config";
|
|||||||
export async function apiAdminForum({
|
export async function apiAdminForum({
|
||||||
category,
|
category,
|
||||||
search,
|
search,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
category: "dashboard" | "posting" | "report_posting" | "report_comment";
|
category: "dashboard" | "posting" | "report_posting" | "report_comment";
|
||||||
search?: string;
|
search?: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/admin/forum?category=${category}&search=${search}`
|
`/mobile/admin/forum?category=${category}&search=${search}&page=${page}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -28,13 +30,15 @@ export async function apiAdminForumPostingById({ id }: { id: string }) {
|
|||||||
export async function apiAdminForumCommentById({
|
export async function apiAdminForumCommentById({
|
||||||
id,
|
id,
|
||||||
category,
|
category,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
category: "get-all" | "get-one";
|
category: "get-all" | "get-one";
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/admin/forum/${id}/comment?category=${category}`
|
`/mobile/admin/forum/${id}/comment?category=${category}&page=${page}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -44,12 +48,14 @@ export async function apiAdminForumCommentById({
|
|||||||
|
|
||||||
export async function apiAdminForumListReportCommentById({
|
export async function apiAdminForumListReportCommentById({
|
||||||
id,
|
id,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/admin/forum/${id}/report-comment`
|
`/mobile/admin/forum/${id}/report-comment?page=${page}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -78,12 +84,14 @@ export async function apiAdminForumDeactivateComment({
|
|||||||
|
|
||||||
export async function apiAdminForumListReportPostingById({
|
export async function apiAdminForumListReportPostingById({
|
||||||
id,
|
id,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/admin/forum/${id}/report-posting`
|
`/mobile/admin/forum/${id}/report-posting?page=${page}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import { apiConfig } from "../api-config";
|
|||||||
export async function apiAdminInvestment({
|
export async function apiAdminInvestment({
|
||||||
category,
|
category,
|
||||||
search,
|
search,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
category: "dashboard" | "publish" | "review" | "reject";
|
category: "dashboard" | "publish" | "review" | "reject";
|
||||||
search?: string;
|
search?: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
const propsQuery =
|
const propsQuery =
|
||||||
category === "dashboard"
|
category === "dashboard"
|
||||||
? `category=${category}`
|
? `category=${category}&page=${page}`
|
||||||
: `category=${category}&search=${search}`;
|
: `category=${category}&search=${search}&page=${page}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
@@ -57,15 +59,23 @@ export async function apiAdminInvestasiUpdateByStatus({
|
|||||||
export async function apiAdminInvestmentListOfInvestor({
|
export async function apiAdminInvestmentListOfInvestor({
|
||||||
id,
|
id,
|
||||||
status,
|
status,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
status: "berhasil" | "gagal" | "proses" | "menunggu" | null;
|
status: "berhasil" | "gagal" | "proses" | "menunggu" | null;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
const query = status && status !== null ? `?status=${status}` : "";
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
|
if (status && status !== null) {
|
||||||
|
queryParams.append("status", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams.append("page", page);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/admin/investment/${id}/investor${query}`
|
`/mobile/admin/investment/${id}/investor?${queryParams.toString()}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user