Merge pull request 'Fix Load data pada halaman yang membutuhkan infinite load' (#45) from loaddata/2-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi-mobile/pulls/45
This commit is contained in:
179
QWEN.md
179
QWEN.md
@@ -1,179 +0,0 @@
|
|||||||
# HIPMI Mobile Application - Development Guide
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
HIPMI Badung Connect is a mobile application built with Expo and React Native. It serves as a connection platform for HIPMI (Himpunan Pengusaha Muda Indonesia) Badung members, featuring authentication, user management, and various business-related functionalities.
|
|
||||||
|
|
||||||
### Key Technologies
|
|
||||||
- **Framework**: Expo (v54.0.0) with React Native (0.81.4)
|
|
||||||
- **Architecture**: File-based routing with Expo Router
|
|
||||||
- **State Management**: React Context API
|
|
||||||
- **Styling**: React Native components with custom color palettes
|
|
||||||
- **Authentication**: Token-based authentication with OTP verification
|
|
||||||
- **Database**: AsyncStorage for local storage
|
|
||||||
- **Maps**: React Native Maps and Mapbox integration
|
|
||||||
- **Notifications**: Expo Notifications and Firebase Messaging
|
|
||||||
- **Language**: TypeScript
|
|
||||||
|
|
||||||
### Project Structure
|
|
||||||
```
|
|
||||||
hipmi-mobile/
|
|
||||||
├── app/ # File-based routing structure
|
|
||||||
│ ├── (application)/ # Main application screens
|
|
||||||
│ │ ├── (file)/ # File management screens
|
|
||||||
│ │ ├── (image)/ # Image management screens
|
|
||||||
│ │ ├── (user)/ # User-specific screens
|
|
||||||
│ │ └── admin/ # Admin-specific screens
|
|
||||||
│ ├── _layout.tsx # Root layout wrapper
|
|
||||||
│ ├── index.tsx # Home screen
|
|
||||||
│ ├── eula.tsx # Terms and conditions screen
|
|
||||||
│ ├── register.tsx # Registration screen
|
|
||||||
│ └── verification.tsx # OTP verification screen
|
|
||||||
├── assets/ # Static assets (images, icons)
|
|
||||||
├── components/ # Reusable UI components
|
|
||||||
├── constants/ # Configuration constants
|
|
||||||
├── context/ # React Context providers
|
|
||||||
├── hooks/ # Custom React hooks
|
|
||||||
├── screens/ # Screen components
|
|
||||||
├── service/ # API services and configurations
|
|
||||||
├── types/ # TypeScript type definitions
|
|
||||||
├── app.config.js # Expo configuration
|
|
||||||
├── package.json # Dependencies and scripts
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building and Running
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
- Node.js (with bun >=1.0.0 as specified in package.json)
|
|
||||||
- Expo CLI or bun installed globally
|
|
||||||
|
|
||||||
### Setup Instructions
|
|
||||||
1. **Install dependencies**:
|
|
||||||
```bash
|
|
||||||
bun install
|
|
||||||
# or if using npm
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Environment Variables**:
|
|
||||||
Create a `.env` file with the following variables:
|
|
||||||
```
|
|
||||||
API_BASE_URL=your_api_base_url
|
|
||||||
BASE_URL=your_base_url
|
|
||||||
DEEP_LINK_URL=your_deep_link_url
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Start the development server**:
|
|
||||||
```bash
|
|
||||||
# Using bun (as specified in package.json)
|
|
||||||
bun run start
|
|
||||||
# or using expo directly
|
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Platform-specific commands**:
|
|
||||||
```bash
|
|
||||||
# Android
|
|
||||||
bun run android
|
|
||||||
# iOS
|
|
||||||
bun run ios
|
|
||||||
# Web
|
|
||||||
bun run web
|
|
||||||
```
|
|
||||||
|
|
||||||
### EAS Build Configuration
|
|
||||||
The project uses Expo Application Services (EAS) for building and deployment:
|
|
||||||
- Development builds: `eas build --profile development`
|
|
||||||
- Preview builds: `eas build --profile preview`
|
|
||||||
- Production builds: `eas build --profile production`
|
|
||||||
|
|
||||||
## Authentication Flow
|
|
||||||
|
|
||||||
The application implements a phone number-based authentication system with OTP verification:
|
|
||||||
|
|
||||||
1. **Login**: User enters phone number → OTP sent via SMS
|
|
||||||
2. **Verification**: User enters OTP code → Validates and creates session
|
|
||||||
3. **Registration**: If user doesn't exist, registration flow is triggered
|
|
||||||
4. **Terms Agreement**: User must accept terms and conditions
|
|
||||||
5. **Session Management**: Tokens stored in AsyncStorage
|
|
||||||
|
|
||||||
### Key Authentication Functions
|
|
||||||
- `loginWithNomor()`: Initiates OTP sending
|
|
||||||
- `validateOtp()`: Verifies OTP and creates session
|
|
||||||
- `registerUser()`: Registers new users
|
|
||||||
- `logout()`: Clears session and removes tokens
|
|
||||||
- `acceptedTerms()`: Handles terms acceptance
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### User Management
|
|
||||||
- Phone number-based registration and login
|
|
||||||
- OTP verification system
|
|
||||||
- Terms and conditions agreement
|
|
||||||
- User profile management
|
|
||||||
|
|
||||||
### Business Features
|
|
||||||
- Business field management (admin section)
|
|
||||||
- File and image management capabilities
|
|
||||||
- Location services integration
|
|
||||||
- Push notifications
|
|
||||||
|
|
||||||
### UI Components
|
|
||||||
- Custom color palette with blue/yellow theme
|
|
||||||
- Responsive layouts using SafeAreaView
|
|
||||||
- Toast notifications for user feedback
|
|
||||||
- Bottom tab navigation and drawer navigation
|
|
||||||
|
|
||||||
## Development Conventions
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
- Components: PascalCase (e.g., `UserProfile.tsx`)
|
|
||||||
- Functions: camelCase (e.g., `getUserData()`)
|
|
||||||
- Constants: UPPER_SNAKE_CASE (e.g., `API_BASE_URL`)
|
|
||||||
- Files: kebab-case or camelCase for utility files
|
|
||||||
|
|
||||||
### Code Organization
|
|
||||||
- Components are organized by feature/functionality
|
|
||||||
- API services are centralized in the `service/` directory
|
|
||||||
- Type definitions are maintained in the `types/` directory
|
|
||||||
- Constants are grouped by category in the `constants/` directory
|
|
||||||
|
|
||||||
### Styling Approach
|
|
||||||
- Color palette defined in `constants/color-palet.ts`
|
|
||||||
- Reusable styles and themes centralized
|
|
||||||
- Responsive design using React Native's flexbox system
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
- Linting: `bun run lint` (uses ESLint with Expo config)
|
|
||||||
- No specific test framework mentioned in package.json
|
|
||||||
|
|
||||||
## Environment Configuration
|
|
||||||
|
|
||||||
The application supports multiple environments through:
|
|
||||||
- Environment variables loaded via dotenv
|
|
||||||
- Expo's extra configuration in `app.config.js`
|
|
||||||
- Platform-specific configurations for iOS and Android
|
|
||||||
|
|
||||||
### Supported Platforms
|
|
||||||
- iOS (with tablet support)
|
|
||||||
- Android (with adaptive icons)
|
|
||||||
- Web (static output)
|
|
||||||
|
|
||||||
## Third-party Integrations
|
|
||||||
|
|
||||||
- **Firebase**: Authentication, messaging, and analytics
|
|
||||||
- **Mapbox**: Advanced mapping capabilities
|
|
||||||
- **React Navigation**: Screen navigation and routing
|
|
||||||
- **React Native Paper**: Material Design components
|
|
||||||
- **Axios**: HTTP client for API requests
|
|
||||||
- **Lodash**: Utility functions
|
|
||||||
- **QR Code SVG**: QR code generation
|
|
||||||
|
|
||||||
## Important Configuration Files
|
|
||||||
|
|
||||||
- `app.config.js`: Expo configuration, app metadata, and plugin setup
|
|
||||||
- `eas.json`: EAS build profiles and submission configuration
|
|
||||||
- `tsconfig.json`: TypeScript compiler options
|
|
||||||
- `package.json`: Dependencies, scripts, and project metadata
|
|
||||||
- `metro.config.js`: Metro bundler configuration
|
|
||||||
@@ -1,57 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { BaseBox, LoaderCustom, TextCustom, ViewWrapper } from "@/components";
|
import Job_ScreenArchive2 from "@/screens/Job/ScreenArchive2";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
|
||||||
import { apiJobGetAll } from "@/service/api-client/api-job";
|
|
||||||
import { useFocusEffect } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
|
|
||||||
export default function JobArchive() {
|
export default function JobArchive() {
|
||||||
const { user } = useAuth();
|
|
||||||
const [listData, setListData] = useState<any[]>([]);
|
|
||||||
const [isLoadData, setIsLoadData] = useState(false);
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [user?.id])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setIsLoadData(true);
|
|
||||||
const response = await apiJobGetAll({
|
|
||||||
category: "archive",
|
|
||||||
authorId: user?.id,
|
|
||||||
});
|
|
||||||
setListData(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
} finally {
|
|
||||||
setIsLoadData(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper hideFooter>
|
<>
|
||||||
{isLoadData ? (
|
<Job_ScreenArchive2 />
|
||||||
<LoaderCustom />
|
</>
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<TextCustom align="center">Anda tidak memiliki arsip</TextCustom>
|
|
||||||
) : (
|
|
||||||
listData.map((item, index) => (
|
|
||||||
<BaseBox
|
|
||||||
key={index}
|
|
||||||
paddingTop={20}
|
|
||||||
paddingBottom={20}
|
|
||||||
href={`/job/${item.id}/archive`}
|
|
||||||
>
|
|
||||||
<TextCustom align="center" bold truncate size="large">
|
|
||||||
{item?.title || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
</BaseBox>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ViewWrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,10 @@
|
|||||||
import {
|
import Job_ScreenBeranda from "@/screens/Job/ScreenBeranda";
|
||||||
AvatarUsernameAndOtherComponent,
|
import Job_ScreenBeranda2 from "@/screens/Job/ScreenBeranda2";
|
||||||
BoxWithHeaderSection,
|
|
||||||
FloatingButton,
|
|
||||||
LoaderCustom,
|
|
||||||
SearchInput,
|
|
||||||
Spacing,
|
|
||||||
StackCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
import { apiJobGetAll } from "@/service/api-client/api-job";
|
|
||||||
import { router, useFocusEffect } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
|
|
||||||
export default function JobBeranda() {
|
export default function JobBeranda() {
|
||||||
const [listData, setListData] = useState<any[]>([]);
|
|
||||||
const [isLoadData, setIsLoadData] = useState(false);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData(search);
|
|
||||||
}, [search])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async (search: string) => {
|
|
||||||
try {
|
|
||||||
setIsLoadData(true);
|
|
||||||
const response = await apiJobGetAll({ search, category: "beranda" });
|
|
||||||
setListData(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
} finally {
|
|
||||||
setIsLoadData(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearch = (search: string) => {
|
|
||||||
setSearch(search);
|
|
||||||
onLoadData(search);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper
|
<>
|
||||||
hideFooter
|
<Job_ScreenBeranda2 />
|
||||||
floatingButton={
|
</>
|
||||||
<FloatingButton onPress={() => router.push("/job/create")} />
|
|
||||||
}
|
|
||||||
headerComponent={
|
|
||||||
<SearchInput placeholder="Cari pekerjaan" onChangeText={handleSearch} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{isLoadData ? (
|
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<TextCustom align="center">Belum ada lowongan</TextCustom>
|
|
||||||
) : (
|
|
||||||
listData.map((item, index) => (
|
|
||||||
<BoxWithHeaderSection
|
|
||||||
key={index}
|
|
||||||
onPress={() => router.push(`/job/${item.id}`)}
|
|
||||||
>
|
|
||||||
<StackCustom>
|
|
||||||
<AvatarUsernameAndOtherComponent
|
|
||||||
avatar={item?.Author?.Profile?.imageId}
|
|
||||||
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
|
||||||
name={item?.Author?.username}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextCustom truncate={2} align="center" bold size="large">
|
|
||||||
{item?.title || "-"}
|
|
||||||
</TextCustom>
|
|
||||||
</StackCustom>
|
|
||||||
<Spacing />
|
|
||||||
</BoxWithHeaderSection>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
<Spacing />
|
|
||||||
</ViewWrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +1,12 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import Job_MainViewStatus from "@/screens/Job/MainViewStatus";
|
||||||
BaseBox,
|
import Job_MainViewStatus2 from "@/screens/Job/MainViewStatus2";
|
||||||
LoaderCustom,
|
|
||||||
ScrollableCustom,
|
|
||||||
TextCustom,
|
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
|
||||||
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
|
||||||
import { apiJobGetByStatus } from "@/service/api-client/api-job";
|
|
||||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { useCallback, useState } from "react";
|
|
||||||
|
|
||||||
export default function JobStatus() {
|
export default function JobStatus() {
|
||||||
const { user } = useAuth();
|
|
||||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
|
||||||
console.log("STATUS", status);
|
|
||||||
|
|
||||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
|
||||||
status || "publish"
|
|
||||||
);
|
|
||||||
const [listData, setListData] = useState<any[]>([]);
|
|
||||||
const [isLoadList, setIsLoadList] = useState(false);
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
onLoadData();
|
|
||||||
}, [user?.id, activeCategory])
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLoadData = async () => {
|
|
||||||
try {
|
|
||||||
setIsLoadList(true);
|
|
||||||
const response = await apiJobGetByStatus({
|
|
||||||
authorId: user?.id as string,
|
|
||||||
status: activeCategory as string,
|
|
||||||
});
|
|
||||||
setListData(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("[ERROR]", error);
|
|
||||||
} finally {
|
|
||||||
setIsLoadList(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePress = (item: any) => {
|
|
||||||
setActiveCategory(item.value);
|
|
||||||
// tambahkan logika lain seperti filter dsb.
|
|
||||||
};
|
|
||||||
|
|
||||||
const scrollComponent = (
|
|
||||||
<ScrollableCustom
|
|
||||||
data={dummyMasterStatus.map((e, i) => ({
|
|
||||||
id: i,
|
|
||||||
label: e.label,
|
|
||||||
value: e.value,
|
|
||||||
}))}
|
|
||||||
onButtonPress={handlePress}
|
|
||||||
activeId={activeCategory as any}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewWrapper headerComponent={scrollComponent} hideFooter>
|
{/* <Job_MainViewStatus /> */}
|
||||||
{isLoadList ? (
|
<Job_MainViewStatus2 />
|
||||||
<LoaderCustom />
|
|
||||||
) : _.isEmpty(listData) ? (
|
|
||||||
<TextCustom align="center">
|
|
||||||
Tidak ada data {activeCategory}
|
|
||||||
</TextCustom>
|
|
||||||
) : (
|
|
||||||
listData.map((e, i) => (
|
|
||||||
<BaseBox
|
|
||||||
key={i}
|
|
||||||
paddingTop={20}
|
|
||||||
paddingBottom={20}
|
|
||||||
href={`/job/${e?.id}/${activeCategory}/detail`}
|
|
||||||
>
|
|
||||||
<TextCustom align="center" bold truncate size="large">
|
|
||||||
{e?.title}
|
|
||||||
</TextCustom>
|
|
||||||
</BaseBox>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ViewWrapper>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
|
BoxButtonOnFooter,
|
||||||
ButtonCenteredOnly,
|
ButtonCenteredOnly,
|
||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
InformationBox,
|
InformationBox,
|
||||||
LandscapeFrameUploaded,
|
LandscapeFrameUploaded,
|
||||||
|
NewWrapper,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextAreaCustom,
|
TextAreaCustom,
|
||||||
TextInputCustom,
|
TextInputCustom
|
||||||
ViewWrapper
|
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import DIRECTORY_ID from "@/constants/directory-id";
|
import DIRECTORY_ID from "@/constants/directory-id";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
@@ -99,16 +100,17 @@ export default function JobCreate() {
|
|||||||
const buttonSubmit = () => {
|
const buttonSubmit = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
|
<BoxButtonOnFooter>
|
||||||
Simpan
|
<ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
|
||||||
</ButtonCustom>
|
Simpan
|
||||||
<Spacing />
|
</ButtonCustom>
|
||||||
|
</BoxButtonOnFooter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper>
|
<NewWrapper footerComponent={buttonSubmit()}>
|
||||||
<StackCustom gap={"xs"}>
|
<StackCustom gap={"xs"}>
|
||||||
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
|
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
|
||||||
|
|
||||||
@@ -160,9 +162,7 @@ export default function JobCreate() {
|
|||||||
value={data.deskripsi}
|
value={data.deskripsi}
|
||||||
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
onChangeText={(value) => setData({ ...data, deskripsi: value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{buttonSubmit()}
|
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
</ViewWrapper>
|
</NewWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<!-- Start Penerapan Pagination -->
|
<!-- Start Penerapan Pagination -->
|
||||||
|
|
||||||
File utama: screens/Notification/ScreenNotification.tsx
|
File utama: screens/Job/ScreenArchive2.tsx
|
||||||
Fun fecth: apiGetNotificationsById
|
Fun fecth: apiJobGetByStatus
|
||||||
File fetch: service/api-notifications.ts
|
File fetch: service/api-client/api-job.ts
|
||||||
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
|
||||||
|
|
||||||
Terapkan pagination pada file "File utama"
|
Terapkan pagination pada file "File utama"
|
||||||
|
|||||||
91
screens/Job/MainViewStatus.tsx
Normal file
91
screens/Job/MainViewStatus.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import {
|
||||||
|
BaseBox,
|
||||||
|
LoaderCustom,
|
||||||
|
ScrollableCustom,
|
||||||
|
TextCustom,
|
||||||
|
ViewWrapper,
|
||||||
|
} from "@/components";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||||
|
import { apiJobGetByStatus } from "@/service/api-client/api-job";
|
||||||
|
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
export default function Job_MainViewStatus() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||||
|
console.log("STATUS", status);
|
||||||
|
|
||||||
|
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||||
|
status || "publish"
|
||||||
|
);
|
||||||
|
const [listData, setListData] = useState<any[]>([]);
|
||||||
|
const [isLoadList, setIsLoadList] = useState(false);
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
onLoadData();
|
||||||
|
}, [user?.id, activeCategory])
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLoadData = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoadList(true);
|
||||||
|
const response = await apiJobGetByStatus({
|
||||||
|
authorId: user?.id as string,
|
||||||
|
status: activeCategory as string,
|
||||||
|
});
|
||||||
|
setListData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoadList(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePress = (item: any) => {
|
||||||
|
setActiveCategory(item.value);
|
||||||
|
// tambahkan logika lain seperti filter dsb.
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollComponent = (
|
||||||
|
<ScrollableCustom
|
||||||
|
data={dummyMasterStatus.map((e, i) => ({
|
||||||
|
id: i,
|
||||||
|
label: e.label,
|
||||||
|
value: e.value,
|
||||||
|
}))}
|
||||||
|
onButtonPress={handlePress}
|
||||||
|
activeId={activeCategory as any}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ViewWrapper headerComponent={scrollComponent} hideFooter>
|
||||||
|
{isLoadList ? (
|
||||||
|
<LoaderCustom />
|
||||||
|
) : _.isEmpty(listData) ? (
|
||||||
|
<TextCustom align="center">
|
||||||
|
Tidak ada data {activeCategory}
|
||||||
|
</TextCustom>
|
||||||
|
) : (
|
||||||
|
listData.map((e, i) => (
|
||||||
|
<BaseBox
|
||||||
|
key={i}
|
||||||
|
paddingTop={20}
|
||||||
|
paddingBottom={20}
|
||||||
|
href={`/job/${e?.id}/${activeCategory}/detail`}
|
||||||
|
>
|
||||||
|
<TextCustom align="center" bold truncate size="large">
|
||||||
|
{e?.title}
|
||||||
|
</TextCustom>
|
||||||
|
</BaseBox>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ViewWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
screens/Job/MainViewStatus2.tsx
Normal file
111
screens/Job/MainViewStatus2.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import {
|
||||||
|
BaseBox,
|
||||||
|
ScrollableCustom,
|
||||||
|
TextCustom,
|
||||||
|
ViewWrapper,
|
||||||
|
} from "@/components";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
|
||||||
|
import { apiJobGetByStatus } from "@/service/api-client/api-job";
|
||||||
|
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { RefreshControl, View } from "react-native";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
|
||||||
|
export default function Job_MainViewStatus2() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||||
|
console.log("STATUS", status);
|
||||||
|
|
||||||
|
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||||
|
status || "publish"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page) => {
|
||||||
|
if (!user?.id) return { data: [] };
|
||||||
|
|
||||||
|
return await apiJobGetByStatus({
|
||||||
|
authorId: user?.id as string,
|
||||||
|
status: activeCategory as string,
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
dependencies: [user?.id, activeCategory],
|
||||||
|
onError: (error) => console.error("[ERROR] Fetch job by status:", error),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate komponen
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
emptyMessage: `Tidak ada data ${activeCategory}`,
|
||||||
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
|
skeletonHeight: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render item job
|
||||||
|
const renderJobItem = ({ item }: { item: any }) => (
|
||||||
|
<BaseBox
|
||||||
|
key={item.id}
|
||||||
|
paddingTop={20}
|
||||||
|
paddingBottom={20}
|
||||||
|
href={`/job/${item?.id}/${activeCategory}/detail`}
|
||||||
|
>
|
||||||
|
<TextCustom align="center" bold truncate size="large">
|
||||||
|
{item?.title}
|
||||||
|
</TextCustom>
|
||||||
|
</BaseBox>
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePress = (item: any) => {
|
||||||
|
setActiveCategory(item.value);
|
||||||
|
// Reset pagination saat kategori berubah
|
||||||
|
pagination.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollComponent = (
|
||||||
|
<ScrollableCustom
|
||||||
|
data={dummyMasterStatus.map((e, i) => ({
|
||||||
|
id: i,
|
||||||
|
label: e.label,
|
||||||
|
value: e.value,
|
||||||
|
}))}
|
||||||
|
onButtonPress={handlePress}
|
||||||
|
activeId={activeCategory as any}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewWrapper
|
||||||
|
headerComponent={
|
||||||
|
<View style={{ paddingTop: 8 }}>
|
||||||
|
{scrollComponent}
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderJobItem}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
tintColor={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
hideFooter
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
screens/Job/ScreenArchive.tsx
Normal file
57
screens/Job/ScreenArchive.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { BaseBox, LoaderCustom, TextCustom, ViewWrapper } from "@/components";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { apiJobGetAll } from "@/service/api-client/api-job";
|
||||||
|
import { useFocusEffect } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
export default function Job_ScreenArchive() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const [listData, setListData] = useState<any[]>([]);
|
||||||
|
const [isLoadData, setIsLoadData] = useState(false);
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
onLoadData();
|
||||||
|
}, [user?.id])
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLoadData = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoadData(true);
|
||||||
|
const response = await apiJobGetAll({
|
||||||
|
category: "archive",
|
||||||
|
authorId: user?.id,
|
||||||
|
});
|
||||||
|
setListData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoadData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewWrapper hideFooter>
|
||||||
|
{isLoadData ? (
|
||||||
|
<LoaderCustom />
|
||||||
|
) : _.isEmpty(listData) ? (
|
||||||
|
<TextCustom align="center">Anda tidak memiliki arsip</TextCustom>
|
||||||
|
) : (
|
||||||
|
listData.map((item, index) => (
|
||||||
|
<BaseBox
|
||||||
|
key={index}
|
||||||
|
paddingTop={20}
|
||||||
|
paddingBottom={20}
|
||||||
|
href={`/job/${item.id}/archive`}
|
||||||
|
>
|
||||||
|
<TextCustom align="center" bold truncate size="large">
|
||||||
|
{item?.title || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
</BaseBox>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
screens/Job/ScreenArchive2.tsx
Normal file
76
screens/Job/ScreenArchive2.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { BaseBox, TextCustom, ViewWrapper } from "@/components";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { apiJobGetAll } from "@/service/api-client/api-job";
|
||||||
|
import { useFocusEffect } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { RefreshControl } from "react-native";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
|
||||||
|
export default function Job_ScreenArchive2() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
// Setup pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page) => {
|
||||||
|
if (!user?.id) return { data: [] };
|
||||||
|
|
||||||
|
return await apiJobGetAll({
|
||||||
|
category: "archive",
|
||||||
|
authorId: user?.id,
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
dependencies: [user?.id],
|
||||||
|
onError: (error) => console.error("[ERROR] Fetch job archive:", error),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate komponen
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
emptyMessage: "Anda tidak memiliki arsip",
|
||||||
|
skeletonCount: PAGINATION_DEFAULT_TAKE,
|
||||||
|
skeletonHeight: 80,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render item job
|
||||||
|
const renderJobItem = ({ item }: { item: any }) => (
|
||||||
|
<BaseBox
|
||||||
|
key={item.id}
|
||||||
|
paddingTop={20}
|
||||||
|
paddingBottom={20}
|
||||||
|
href={`/job/${item.id}/archive`}
|
||||||
|
>
|
||||||
|
<TextCustom align="center" bold truncate size="large">
|
||||||
|
{item?.title || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
</BaseBox>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderJobItem}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
tintColor={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
hideFooter
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
83
screens/Job/ScreenBeranda.tsx
Normal file
83
screens/Job/ScreenBeranda.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
AvatarUsernameAndOtherComponent,
|
||||||
|
BoxWithHeaderSection,
|
||||||
|
FloatingButton,
|
||||||
|
LoaderCustom,
|
||||||
|
SearchInput,
|
||||||
|
Spacing,
|
||||||
|
StackCustom,
|
||||||
|
TextCustom,
|
||||||
|
ViewWrapper,
|
||||||
|
} from "@/components";
|
||||||
|
import { apiJobGetAll } from "@/service/api-client/api-job";
|
||||||
|
import { router, useFocusEffect } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
export default function Job_ScreenBeranda() {
|
||||||
|
const [listData, setListData] = useState<any[]>([]);
|
||||||
|
const [isLoadData, setIsLoadData] = useState(false);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
onLoadData(search);
|
||||||
|
}, [search])
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLoadData = async (search: string) => {
|
||||||
|
try {
|
||||||
|
setIsLoadData(true);
|
||||||
|
const response = await apiJobGetAll({ search, category: "beranda" });
|
||||||
|
setListData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("[ERROR]", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoadData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (search: string) => {
|
||||||
|
setSearch(search);
|
||||||
|
onLoadData(search);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewWrapper
|
||||||
|
hideFooter
|
||||||
|
floatingButton={
|
||||||
|
<FloatingButton onPress={() => router.push("/job/create")} />
|
||||||
|
}
|
||||||
|
headerComponent={
|
||||||
|
<SearchInput placeholder="Cari pekerjaan" onChangeText={handleSearch} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isLoadData ? (
|
||||||
|
<LoaderCustom />
|
||||||
|
) : _.isEmpty(listData) ? (
|
||||||
|
<TextCustom align="center">Belum ada lowongan</TextCustom>
|
||||||
|
) : (
|
||||||
|
listData.map((item, index) => (
|
||||||
|
<BoxWithHeaderSection
|
||||||
|
key={index}
|
||||||
|
onPress={() => router.push(`/job/${item.id}`)}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<AvatarUsernameAndOtherComponent
|
||||||
|
avatar={item?.Author?.Profile?.imageId}
|
||||||
|
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||||
|
name={item?.Author?.username}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextCustom truncate={2} align="center" bold size="large">
|
||||||
|
{item?.title || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
</StackCustom>
|
||||||
|
<Spacing />
|
||||||
|
</BoxWithHeaderSection>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
<Spacing />
|
||||||
|
</ViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
screens/Job/ScreenBeranda2.tsx
Normal file
105
screens/Job/ScreenBeranda2.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import {
|
||||||
|
AvatarUsernameAndOtherComponent,
|
||||||
|
BoxWithHeaderSection,
|
||||||
|
FloatingButton,
|
||||||
|
SearchInput,
|
||||||
|
Spacing,
|
||||||
|
StackCustom,
|
||||||
|
TextCustom,
|
||||||
|
ViewWrapper,
|
||||||
|
} from "@/components";
|
||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
|
import { usePagination } from "@/hooks/use-pagination";
|
||||||
|
import { apiJobGetAll } from "@/service/api-client/api-job";
|
||||||
|
import { router, useFocusEffect } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { RefreshControl, View } from "react-native";
|
||||||
|
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
|
||||||
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
export default function Job_ScreenBeranda2() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
// Setup pagination
|
||||||
|
const pagination = usePagination({
|
||||||
|
fetchFunction: async (page, searchQuery) => {
|
||||||
|
return await apiJobGetAll({
|
||||||
|
search: searchQuery || "",
|
||||||
|
category: "beranda",
|
||||||
|
page: String(page),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
pageSize: PAGINATION_DEFAULT_TAKE,
|
||||||
|
searchQuery: search,
|
||||||
|
dependencies: [],
|
||||||
|
onError: (error) => console.error("[ERROR] Fetch job:", error),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate komponen
|
||||||
|
const { ListEmptyComponent, ListFooterComponent } =
|
||||||
|
createPaginationComponents({
|
||||||
|
loading: pagination.loading,
|
||||||
|
refreshing: pagination.refreshing,
|
||||||
|
listData: pagination.listData,
|
||||||
|
searchQuery: search,
|
||||||
|
emptyMessage: "Belum ada lowongan",
|
||||||
|
emptySearchMessage: "Tidak ada hasil pencarian",
|
||||||
|
skeletonCount: 5,
|
||||||
|
skeletonHeight: 150,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render item job
|
||||||
|
const renderJobItem = ({ item }: { item: any }) => (
|
||||||
|
<BoxWithHeaderSection
|
||||||
|
key={item.id}
|
||||||
|
onPress={() => router.push(`/job/${item.id}`)}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<AvatarUsernameAndOtherComponent
|
||||||
|
avatar={item?.Author?.Profile?.imageId}
|
||||||
|
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
|
||||||
|
name={item?.Author?.username}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextCustom truncate={2} align="center" bold size="large">
|
||||||
|
{item?.title || "-"}
|
||||||
|
</TextCustom>
|
||||||
|
</StackCustom>
|
||||||
|
<Spacing />
|
||||||
|
</BoxWithHeaderSection>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewWrapper
|
||||||
|
hideFooter
|
||||||
|
headerComponent={
|
||||||
|
<View style={{ paddingTop: 8 }}>
|
||||||
|
<SearchInput
|
||||||
|
placeholder="Cari pekerjaan"
|
||||||
|
onChangeText={_.debounce((text) => setSearch(text), 500)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
floatingButton={
|
||||||
|
<FloatingButton onPress={() => router.push("/job/create")} />
|
||||||
|
}
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderJobItem}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
tintColor={MainColor.yellow}
|
||||||
|
colors={[MainColor.yellow]}
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.onRefresh}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -136,6 +136,7 @@ export default function ScreenNotification() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handlePress = (item: any) => {
|
const handlePress = (item: any) => {
|
||||||
|
console.log("ITEM", item.value);
|
||||||
setActiveCategory(item.value);
|
setActiveCategory(item.value);
|
||||||
// Reset and load first page when category changes
|
// Reset and load first page when category changes
|
||||||
pagination.reset();
|
pagination.reset();
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ export async function apiJobCreate(data: any) {
|
|||||||
export async function apiJobGetByStatus({
|
export async function apiJobGetByStatus({
|
||||||
authorId,
|
authorId,
|
||||||
status,
|
status,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
authorId: string;
|
authorId: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const response = await apiConfig.get(`/mobile/job/${authorId}/${status}`);
|
const response = await apiConfig.get(`/mobile/job/${authorId}/${status}?page=${page}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -63,10 +65,12 @@ export async function apiJobGetAll({
|
|||||||
search,
|
search,
|
||||||
category,
|
category,
|
||||||
authorId,
|
authorId,
|
||||||
|
page = "1",
|
||||||
}: {
|
}: {
|
||||||
search?: string;
|
search?: string;
|
||||||
category: "archive" | "beranda";
|
category: "archive" | "beranda";
|
||||||
authorId?: string;
|
authorId?: string;
|
||||||
|
page?: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
let categoryText = category ? `?category=${category}` : "";
|
let categoryText = category ? `?category=${category}` : "";
|
||||||
@@ -74,8 +78,9 @@ export async function apiJobGetAll({
|
|||||||
categoryText = `?category=${category}&authorId=${authorId}`;
|
categoryText = `?category=${category}&authorId=${authorId}`;
|
||||||
}
|
}
|
||||||
const searchText = search ? `&search=${search}` : "";
|
const searchText = search ? `&search=${search}` : "";
|
||||||
|
const pageText = `&page=${page}`;
|
||||||
const response = await apiConfig.get(
|
const response = await apiConfig.get(
|
||||||
`/mobile/job${categoryText}${searchText}`
|
`/mobile/job${categoryText}${searchText}${pageText}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user