Compare commits

...

51 Commits

Author SHA1 Message Date
9faa0b0f64 Collaboration Mobile API
Add:
- /admin/collaboration/[id]/reject-input.tsx
- /api-admin/api-admin-collaboration.ts

Fix:
- Integrasi ke tampilan UI
app/(application)/admin/collaboration/[id]/[status].tsx
app/(application)/admin/collaboration/index.tsx
app/(application)/admin/collaboration/publish.tsx
components/_ShareComponent/Admin/TableTitle.tsx
components/_ShareComponent/Admin/TableValue.tsx

### No Issue
2025-10-15 17:30:25 +08:00
e05a7c8701 Portofolio
Fix:
(application)/(user)/portofolio/[id]/index.
Button hapus hanya muncul jika user login adalah author

### No Issue
2025-10-15 15:45:01 +08:00
f50c5099d8 Integrasi API: App Information & Maps
Add:
- service/api-admin/api-master-admin.ts

Fix:
app/(application)/admin/app-information/business-field/[id]/index.tsx
app/(application)/admin/app-information/business-field/create.tsx
app/(application)/admin/app-information/index.tsx
app/(application)/admin/app-information/information-bank/[id]/index.tsx
app/(application)/admin/app-information/information-bank/create.tsx
app/(application)/admin/maps.tsx
screens/Admin/App-Information/BusinessFieldSection.tsx
screens/Admin/App-Information/InformationBankSection.tsx
screens/Admin/App-Information/StickerSection.tsx
screens/Authentication/LoginView.tsx
service/api-client/api-master.ts

- Perbaikan berupa integrasi API

### No Issue
2025-10-15 15:09:18 +08:00
5f36620988 Integrasi Admin: User Acces & Super Admin
Add:
- admin/super-admin/
- admin/user-access/
- service/api-admin/

Fix:
- (user)/profile/[id]/index: penambahan useData dari useAuthuntuk merestart value masterRole
- integrasi pada tampilan admin

### No Issue
2025-10-14 17:28:40 +08:00
f750d158be Integrasi Map Business
Add:
 components/Map/MapSelected.tsx
        components/_ShareComponent/GridTwoView.tsx
        service/api-client/api-maps.ts
utils/openInDeviceMaps.ts

Fix:
 modified:   app/(application)/(user)/maps/[id]/edit.tsx
        modified:   app/(application)/(user)/maps/create.tsx
        modified:   app/(application)/(user)/maps/index.tsx
        modified:   app/(application)/(user)/portofolio/[id]/index.tsx
        modified:   components/Map/MapCustom.tsx
        modified:   screens/Portofolio/BusinessLocationSection.tsx
        modified:   screens/Portofolio/DataPortofolio.tsx
        modified:   screens/Portofolio/ListPage.tsx

### No issue
2025-10-13 17:46:47 +08:00
0e7b29bb15 Integrasi API Donation
Fix:
- (application)/(user)/donation/[id]/(news)/[news]/edit-news
- (application)/(user)/donation/[id]/(news)/[news]/index
- (application)/(user)/donation/[id]/(news)/add-news
- (application)/(user)/donation/[id]/(news)/list-of-news
- (application)/(user)/donation/[id]/(news)/recap-of-news
- (application)/(user)/donation/[id]/infromation-fundrising
- service/api-client/api-donation

### No Issue
2025-10-09 17:00:04 +08:00
b293310969 Donation:
Add:
- components/_ShareComponent/MoneyTransferAnimation.tsx

Fix:
- Invoice terintegrasi API
- Create dan list berita

### No Issue
2025-10-08 17:40:36 +08:00
a980397640 Donation
Add:
- (user)/donation/[id]/(transaction-flow)/[invoiceId]

Fix:
Integrasi dalam alur transaksi
- Buat invoice dan list transaksi

### No Issue
2025-10-07 17:40:54 +08:00
7c82e8b588 Donation:
Fix:
- Edit story & bank account
- List beranda dan detailnya

- Tampilan penggalang dana

### No Issue
2025-10-07 15:52:51 +08:00
53cdca21fc Donasi
Fix:
- service/api-client/api-donation.ts: penambahan fetch update, hapus dan update status donasi
- /(application)/(user)/donation/[id]/edit.tsx: integrasi ke API

### No issue
2025-10-06 17:32:18 +08:00
ba878d4d08 Donasi
Fix: tampilan status dan detail status sudah terintegrasi API
- create dan buntton status sudah terintegrasi

### No Issue
2025-10-06 16:11:56 +08:00
f3a3acc747 Donation:
Add:
- service/api-client/api-donation.ts

Fix:
- service/api-client/api-master.ts : tambah master donasi
- app/(application)/(user)/donation/create.tsx
- app/(application)/(user)/donation/create-story.tsx

### No issue
2025-10-03 17:37:29 +08:00
a6389174d7 git add . && git commit -m 2025-10-03 14:09:31 +08:00
2be4afdcb1 Investment
Add:
-  components/Button/CoyButton.tsx
-  constants/local-storage-key.ts

Fix:
- Integrasi pada proses transaksi pmebelian investasi

### No Issue
2025-10-02 17:29:25 +08:00
aa85e05f79 Invesment
Fix: Integrasi API ke UI
- investment/[id]/(transaction-flow)/index.tsx
- investment/[id]/index.tsx

### Issue: input data untuk total lembar dan sisa lembar berisi . dianatra angkanya
2025-10-01 17:29:59 +08:00
c2acb97a37 Invesment
Fix:
- tampilan list data bada beranda dan detail data main

### No Issue
2025-10-01 16:45:23 +08:00
250b216a54 Invesment
Fix:
- tampilan dokumen dan file prospektus
- create & edit dokumen
- list rekap dokumen dan tampilan ( untuk non author )

### No Issue
2025-10-01 14:40:50 +08:00
5f05d1f7f0 Component
Fix: fix nama upload dan delete file service

Invesment:
Fix:
- Edit data dan edit prospektus
- View file dengan metode react-native-webview

### No issue
2025-09-30 17:30:07 +08:00
3d8d8568a3 Invesment
Fix: create & edit list master sudah terintegrasi ke API

### No Issue
2025-09-30 11:00:31 +08:00
ccdd7730b2 Investment
Add:
- utils/pickFile: pilih extention file sesuai kebutuhan
- utils/formatCurrencyDisplay.ts: tampillan uang 2.500
- api-client/api-investment.ts
- api-storage.ts: api strogre wibudev

Fix:
- Integrasi API pada: Create, Edit, Tampilan status & detail
- Button status dan hapus data juga sudah terintegrasi

### No Issue
2025-09-29 17:42:25 +08:00
a474aebb94 Forum:
Fix: link dari avatar ke forumku & penambahan tombol create di forumku jika id forum milik user yang sama seperti user login

### No issue
2025-09-29 10:28:53 +08:00
29b65aeebf Forum
Fix:
- Integrasi API ke semua tampilan

### No Issue
2025-09-26 17:43:50 +08:00
18beb09b42 Forum
Fix:
- Tampilan beranda forum & bisa melakukan search dan sudah terintegrasi API
- Fitur hapus edit dan ubah status sudah terintegrasi API
- List komentar sudah bisa muncul dan bisa mengahpus

### No Issue
2025-09-26 14:46:29 +08:00
54af104f8a Forum
Add:
- api-client/api-forum

Fix:
- Integrasi API: create dan beranda file

### No Issue
2025-09-24 17:30:06 +08:00
8c5602b809 Collaboration
Add:
- Collaboration/GroupChatSection.tsx : fitur room chat

Fix:
- Clear code: Hapus console pada beberapa file

### No Issue
2025-09-24 15:28:16 +08:00
99f058a92f Collaboration
Add:
- (user)/collaboration/[id]/select-of-participants

Fix:
- Integrasi ke api di bagian beranda , partisipan dan group

### No Issue
2025-09-23 17:41:03 +08:00
821a211f58 Collaboration
Fix:
- Integrasi API: Beranda, create, list partisipan, check sudah berpartisipasi ?

### No Issue
2025-09-22 17:31:40 +08:00
333b1d2512 Voting
Fix: Semua tampilan sudah terintegrasi API

### No Issue
2025-09-19 17:51:08 +08:00
391430de46 Voting
Fix:
- Integrasi API pada (tabs) status & detail
- Integrasi API beranda & detail
- Integrasi API pada voting

### No Issue
2025-09-18 17:35:18 +08:00
ce79d7c240 Voting
Add:
- api-client/api-voting: kumpulan fetching api voting

Fix:
- UI create dan (tabs) status udah terintegrasi ke API

### No Isuue
2025-09-17 17:31:44 +08:00
d09a566903 Job
Add:
- add file: (user)/job/[id]/archive

Fix:
- Semua tampilan telah terintergrasi ke API Job

### No Issue
2025-09-17 14:26:10 +08:00
60b0befa60 API Job
Add:
- api-client/api-job: kumpulan fetch api

Fix:
- UI beranda , status sudah terintergrasi dengan API
- UI detail status, detail utama sudah terintergrasi dengan API
- Search pada beranda sudah terintegrasi
- Edit sudah terintergrasi

### No Issue
2025-09-16 17:27:58 +08:00
3287f4c287 Event
Fix:
-  user)/event/(tabs)/contribution: tampilan creator yang di depan
- /Event/ButtonStatusSection: return message

### No Issuue
2025-09-16 11:17:36 +08:00
76fb14ed0c Event
Fix: Intergrasi pada UI
- Beranda, Kontibusi & Riwayat
- Detail beranda, detail kontribusi & detail riwayat
- List partisipan

### No issue
2025-09-15 17:17:50 +08:00
1d2153b253 Event
Fix: Intergrasi tampilan ke API

Package.json
Fix: Pem baharuan SDK53 -> SDK54

### No Issue
2025-09-12 15:57:30 +08:00
005b798688 Event
Fix:
- UI: status, detail status, delete button, detail utama, tampilan utama
- Semua terintergrasi ke API mobile

### No Issue
2025-09-12 14:14:37 +08:00
b6d4c0e6a6 API Event
Add:
- api-client/api-event : fetch get one, update data, update status, create event

Fix:
- UI : create , detail dan status: untuk menyambungkan ke API

### No Issue
2025-09-11 17:34:08 +08:00
3854db9330 Event
Fix:
- Create event
- api master tipe event

## NO Issue
2025-09-10 17:43:01 +08:00
fc181bda7c API
User search:
Fix:
-  api get all user
- searching by username

Portofolio:
Fix:
- dot button hanya muncul jika user yang memiliki portofolio tersebut yang melihat

Profile:
Fix:
- dot button muncul hanya untuk user yang memiliki akunnya

### No Issue
2025-09-10 16:33:39 +08:00
fb822d20b6 Portofolio
Fix: API edit detail data

### No Issue
2025-09-10 12:12:11 +08:00
0e708dde0f API Portofolio
Fix:
- edit select sub bidang

### NO Issue
2025-09-03 17:42:17 +08:00
9a915c55d2 Portofolio
Fix:
- Sub bidang bisnis

### No Issu
2025-09-02 18:29:28 +08:00
6887f85e6a Poortofoloio
Fix:
- service/api-client/api-portofolio.ts : api edit detail. logo, medsos

### No Issue
2025-09-01 17:10:25 +08:00
bb95e8ccbd API
Add:
- service/api-client/api-file.ts: upload & delete

Portofolio
Fix:
- user)/portofolio/[id]/create.tsx: Loading submit
- (user)/portofolio/[id]/index.tsx: Delete button recode

Profile
Fix:
- (user)/profile/[id]/update-photo && upload-backgroud: delete image yang kama

### No Issue
2025-09-01 12:11:21 +08:00
41a4a94255 Portofolio
Add:
- ervice/api-client/api-portofolio.ts
- creens/Portofolio/BoxPortofolioView.tsx
- screens/Portofolio/ButtonCreatePortofolio.tsx
- create dan show

### No Issue
2025-08-29 17:42:33 +08:00
88527d5bb6 Image : fix dummy user
### No Issue
2025-08-28 17:02:38 +08:00
40441c929f API Upload Image
Profile:
- (user)/profile/[id]/update-background.tsx : perbaikan api
-  app/(application)/(user)/profile/[id]/update-photo.tsx

Fix:
-   service/upload-service.ts : ganti Toast menjadi throw untuk dapatkna error

### No Issue
2025-08-28 15:18:15 +08:00
d3c4f04e07 Profile
Add:
- Api upload background
- /api-client/api-validation.ts

### No Issue
2025-08-27 17:41:42 +08:00
4fc2c90702 Profile
Add:
- Api background profile

Asset
Add:
- assets/images/loading.gif: untuk loading

### No Issue
2025-08-27 14:38:37 +08:00
2227aaa99f Profile
Fix:
- profile/[id]/edit.tsx: api upload
- profile/[id]/update-photo.tsx: api upload
- service/api-client/api-profile.ts: api profile bisa memilih kategori

Component
Add:
- components/Image/AvatarComp.tsx

### No Issue
2025-08-27 12:16:31 +08:00
7cddc7abe3 API upload image
Add:
- utils/pickImage.ts
- service/upload-service.ts
- constants/directory-id.ts
- constants/base-url-api-strorage.ts

### No Issue
2025-08-26 17:42:59 +08:00
265 changed files with 18644 additions and 22259 deletions

View File

@@ -26,6 +26,7 @@ export default {
}, },
edgeToEdgeEnabled: true, edgeToEdgeEnabled: true,
package: 'com.bip.hipmimobileapp', package: 'com.bip.hipmimobileapp',
// softwareKeyboardLayoutMode: 'resize', // option: untuk mengatur keyboard pada room chst collaboration
}, },
web: { web: {
@@ -36,6 +37,7 @@ export default {
plugins: [ plugins: [
'expo-router', 'expo-router',
'expo-web-browser',
[ [
'expo-splash-screen', 'expo-splash-screen',
{ {
@@ -67,5 +69,6 @@ export default {
}, },
// Tambahkan environment variables ke sini // Tambahkan environment variables ke sini
API_BASE_URL: process.env.API_BASE_URL, API_BASE_URL: process.env.API_BASE_URL,
BASE_URL: process.env.BASE_URL,
}, },
}; };

View File

@@ -1,9 +1,13 @@
import { BackButton, ViewWrapper } from "@/components"; import { BackButton } from "@/components";
import { MainColor } from "@/constants/color-palet"; import PdfViewer from "@/components/_ShareComponent/PdfViewer";
import { FontAwesome } from "@expo/vector-icons"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import { Stack } from "expo-router"; import { Stack, useLocalSearchParams } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
export default function FileScreen() { export default function FileScreen() {
const { id } = useLocalSearchParams();
const url = API_STRORAGE.GET({ fileId: id as string });
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -12,14 +16,9 @@ export default function FileScreen() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<ViewWrapper> <SafeAreaView style={{ flex: 1 }} edges={["bottom"]}>
<FontAwesome <PdfViewer uri={url} />
name="file-pdf-o" </SafeAreaView>
size={300}
style={{ alignSelf: "center" }}
color={MainColor.white}
/>
</ViewWrapper>
</> </>
); );
} }

View File

@@ -1,9 +1,33 @@
import { LandscapeFrameUploaded, ViewWrapper } from "@/components"; import { CenterCustom, TextCustom, ViewWrapper } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
export default function PreviewImage() { export default function PreviewImage() {
const { id } = useLocalSearchParams();
const [isLoading, setIsLoading] = useState(true);
return ( return (
<ViewWrapper> <ViewWrapper>
<LandscapeFrameUploaded /> {id ? (
<Image
onLoad={() => {
setIsLoading(false);
}}
source={
isLoading
? require("@/assets/images/loading.gif")
: API_STRORAGE.GET({ fileId: id as string })
}
contentFit="contain"
style={{ width: "100%", height: "100%" }}
/>
) : (
<CenterCustom>
<TextCustom>File not found</TextCustom>
</CenterCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -10,7 +10,6 @@ export default function UserLayout() {
return ( return (
<> <>
<Stack screenOptions={HeaderStyles}> <Stack screenOptions={HeaderStyles}>
<Stack.Screen <Stack.Screen
name="waiting-room" name="waiting-room"
options={{ options={{
@@ -118,14 +117,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen {/* <Stack.Screen
name="collaboration/[id]/detail-participant" name="collaboration/[id]/detail-participant"
options={{ options={{
title: "Partisipasi Proyek", title: "Partisipasi Proyek",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> /> */}
<Stack.Screen <Stack.Screen
name="collaboration/[id]/edit" name="collaboration/[id]/edit"
options={{ options={{
@@ -133,6 +131,20 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen
name="collaboration/[id]/create-pacticipants"
options={{
title: "Ajukan Partisipasi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="collaboration/[id]/select-of-participants"
options={{
title: "Pilih Partisipan",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Collaboration Section ========= */} {/* ========== End Collaboration Section ========= */}
@@ -173,7 +185,7 @@ export default function UserLayout() {
name="crowdfunding/index" name="crowdfunding/index"
options={{ options={{
title: "Crowdfunding", title: "Crowdfunding",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton path="/home" />,
}} }}
/> />
@@ -436,7 +448,7 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/invoice" name="donation/[id]/(transaction-flow)/[invoiceId]/invoice"
options={{ options={{
title: "Invoice", title: "Invoice",
headerLeft: () => ( headerLeft: () => (
@@ -450,7 +462,7 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/process" name="donation/[id]/(transaction-flow)/[invoiceId]/process"
options={{ options={{
title: "Proses", title: "Proses",
headerLeft: () => ( headerLeft: () => (
@@ -464,14 +476,14 @@ export default function UserLayout() {
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/success" name="donation/[id]/(transaction-flow)/[invoiceId]/success"
options={{ options={{
title: "Donasi Berhasil", title: "Donasi Berhasil",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="donation/[id]/(transaction-flow)/[transaction]/failed" name="donation/[id]/(transaction-flow)/[invoiceId]/failed"
options={{ options={{
title: "Donasi Gagal", title: "Donasi Gagal",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
@@ -509,6 +521,13 @@ export default function UserLayout() {
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
<Stack.Screen
name="job/[id]/archive"
options={{
title: "Arsip Job",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Job Section ========= */} {/* ========== End Job Section ========= */}

View File

@@ -1,38 +1,96 @@
import { BaseBox, Grid, TextCustom } from "@/components"; /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
Grid,
LoaderCustom,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
export default function CollaborationGroup() { export default function CollaborationGroup() {
const { user } = useAuth();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category: "group",
authorId: user?.id,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
<StackCustom>
{listData?.map((item: any, index: any) => (
<BaseBox <BaseBox
key={index} key={index}
paddingBlock={5} paddingBlock={5}
href={`/collaboration/${index}/${generateProjectName()}/room-chat`} href={`/collaboration/${item?.ProjectCollaboration_RoomChat?.id}/${item?.ProjectCollaboration_RoomChat?.name}/room-chat`}
> >
<Grid> <Grid>
<Grid.Col span={10}> <Grid.Col span={10}>
<TextCustom bold>{generateProjectName()}</TextCustom> <TextCustom bold>
<TextCustom size="small">2 Anggota</TextCustom> {item?.ProjectCollaboration_RoomChat?.name}
</TextCustom>
<TextCustom size="small">
{
item?.ProjectCollaboration_RoomChat
?.ProjectCollaboration_AnggotaRoomChat?.length
}{" "}
Anggota
</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
span={2} span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }} style={{ alignItems: "flex-end", justifyContent: "center" }}
> >
<Feather name="chevron-right" size={20} color={MainColor.white} /> <Feather
name="chevron-right"
size={20}
color={MainColor.white}
/>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))}
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }
function generateProjectName() { function generateProjectName() {
const adjectives = [ const adjectives = [
"Blue", "Blue",

View File

@@ -1,8 +1,40 @@
import { FloatingButton, ViewWrapper } from "@/components"; import {
FloatingButton,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection"; import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { router } from "expo-router"; import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function CollaborationBeranda() { export default function CollaborationBeranda() {
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category: "beranda",
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<> <>
<ViewWrapper <ViewWrapper
@@ -15,13 +47,19 @@ export default function CollaborationBeranda() {
/> />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<Collaboration_BoxPublishSection <Collaboration_BoxPublishSection
key={index} key={index}
id={index.toString()} href={`/collaboration/${item.id}`}
href={`/collaboration/${index}`} data={item}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,15 +1,46 @@
import { ButtonCustom, Spacing } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection"; import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { useState } from "react"; import { apiCollaborationGetAll } from "@/service/api-client/api-collaboration";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function CollaborationParticipans() { export default function CollaborationParticipans() {
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<
"participant" "participant" | "my-project"
>("participant");
const { user } = useAuth();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory])
); );
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetAll({
category:
activeCategory === "participant" ? "participant" : "my-project",
authorId: user?.id,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item); setActiveCategory(item);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -41,13 +72,13 @@ export default function CollaborationParticipans() {
<Spacing width={"2%"} /> <Spacing width={"2%"} />
<ButtonCustom <ButtonCustom
backgroundColor={ backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue activeCategory === "my-project" ? MainColor.yellow : AccentColor.blue
} }
textColor={ textColor={
activeCategory === "main" ? MainColor.black : MainColor.white activeCategory === "my-project" ? MainColor.black : MainColor.white
} }
style={{ width: "49%" }} style={{ width: "49%" }}
onPress={() => handlePress("main")} onPress={() => handlePress("my-project")}
> >
Proyek Saya Proyek Saya
</ButtonCustom> </ButtonCustom>
@@ -56,22 +87,27 @@ export default function CollaborationParticipans() {
return ( return (
<ViewWrapper hideFooter headerComponent={headerComponent}> <ViewWrapper hideFooter headerComponent={headerComponent}>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : activeCategory === "participant" ? (
listData?.map((item: any, index: number) => (
<Collaboration_BoxPublishSection <Collaboration_BoxPublishSection
key={index.toString()} key={index.toString()}
id={index.toString()} data={item?.ProjectCollaboration}
username={` ${ href={`/collaboration/${item?.ProjectCollaboration?.id}/detail-participant`}
activeCategory === "participant"
? "Partisipasi Proyek"
: "Proyek Saya"
}`}
href={
activeCategory === "participant"
? `/collaboration/${index}/detail-participant`
: `/collaboration/${index}/detail-project-main`
}
/> />
))} ))
) : (
listData?.map((item: any, index: number) => (
<Collaboration_BoxPublishSection
key={index.toString()}
data={item}
href={`/collaboration/${item?.id}/detail-project-main`}
/>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,17 +1,41 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BackButton, BackButton,
BaseBox,
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, Grid,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { Stack, useLocalSearchParams } from "expo-router"; import { apiCollaborationGroup } from "@/service/api-client/api-collaboration";
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState, useCallback } from "react";
export default function CollaborationRoomInfo() { export default function CollaborationRoomInfo() {
const { id, detail } = useLocalSearchParams(); const { id, detail } = useLocalSearchParams();
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGroup({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -24,7 +48,7 @@ export default function CollaborationRoomInfo() {
<ViewWrapper> <ViewWrapper>
<BoxWithHeaderSection> <BoxWithHeaderSection>
<StackCustom> <StackCustom>
{listData.map((item, index) => ( {listData({ data }).map((item, index) => (
<Grid key={index}> <Grid key={index}>
<Grid.Col span={4}> <Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom> <TextCustom bold>{item.title}</TextCustom>
@@ -37,37 +61,42 @@ export default function CollaborationRoomInfo() {
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
<BoxWithHeaderSection> <BaseBox>
{Array.from({ length: 10 }).map((_, index) => ( <StackCustom gap={10}>
<AvatarUsernameAndOtherComponent key={index} avatarHref={`/profile/${index}`} /> {data?.ProjectCollaboration_AnggotaRoomChat?.map(
))} (item: any, index: number) => (
</BoxWithHeaderSection> <AvatarUsernameAndOtherComponent
key={index}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
avatar={item?.User?.Profile?.imageId}
/>
)
)}
</StackCustom>
</BaseBox>
</ViewWrapper> </ViewWrapper>
</> </>
); );
} }
const listData = [ const listData = ({ data }: { data: any }) => [
{ {
title: "Judul Proyek", title: "Judul Proyek",
value: "Judul Proyek", value: data?.ProjectCollaboration?.title || "-",
}, },
{ {
title: "Industri", title: "Industri",
value: "Pilihan Industri", value:
}, data?.ProjectCollaboration?.ProjectCollaborationMaster_Industri?.name ||
{ "-",
title: "Deskripsi",
value: "Deskripsi Proyek",
}, },
{ {
title: "Tujuan Proyek", title: "Tujuan Proyek",
value: value: data?.ProjectCollaboration?.purpose || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
{ {
title: "Keuntungan Proyek", title: "Keuntungan Proyek",
value: value: data?.ProjectCollaboration?.benefit || "-",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
}, },
]; ];

View File

@@ -1,68 +1,13 @@
import { import { BackButton } from "@/components";
BackButton, import { MainColor } from "@/constants/color-palet";
BoxButtonOnFooter,
Grid,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import ChatScreen from "@/screens/Collaboration/GroupChatSection";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { StyleSheet, TouchableOpacity, View } from "react-native";
export default function CollaborationRoomChat() { export default function CollaborationRoomChat() {
const { id, detail } = useLocalSearchParams(); const { id, detail } = useLocalSearchParams();
const inputChat = () => {
return (
<>
<BoxButtonOnFooter>
{/* <View style={{flexDirection: 'row', alignItems: 'center'}}>
<TextInputCustom placeholder="Ketik pesan..." />
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log("Send")}
style={{
backgroundColor: AccentColor.blue,
padding: 10,
borderRadius: 50,
}}
>
<Feather name="send" size={30} color={MainColor.white} />
</TouchableOpacity>
</View> */}
<Grid>
<Grid.Col span={9}>
<TextInputCustom placeholder="Ketik pesan..." />
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<TouchableOpacity
activeOpacity={0.5}
onPress={() => console.log("Send")}
style={{
backgroundColor: AccentColor.blue,
padding: 10,
borderRadius: 50,
}}
>
<Feather
name="send"
size={30}
color={MainColor.white}
/>
</TouchableOpacity>
</Grid.Col>
</Grid>
</BoxButtonOnFooter>
</>
);
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -79,114 +24,8 @@ export default function CollaborationRoomChat() {
), ),
}} }}
/> />
<ViewWrapper footerComponent={inputChat()}>
{dummyData.map((item, index) => ( <ChatScreen id={id as string} />
<View
key={index}
style={[
styles.messageRow,
item.role === 1 ? styles.rightAlign : styles.leftAlign,
]}
>
<View
style={[
styles.bubble,
item.role === 1 ? styles.bubbleRight : styles.bubbleLeft,
]}
>
<TextCustom style={styles.sender}>{item.nama}</TextCustom>
<TextCustom style={styles.message}>{item.chat}</TextCustom>
<TextCustom style={styles.time}>
{new Date(item.time).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</TextCustom>
</View>
</View>
))}
{/* <TextInputCustom placeholder="Ketik pesan..." />
<Spacing/> */}
</ViewWrapper>
</> </>
); );
} }
const dummyData = [
{
nama: "Dina",
role: 1,
chat: "Hai! Kamu udah lihat dokumen proyek yang baru?",
time: "2025-07-24T09:01:15Z",
},
{
nama: "Rafi",
role: 2,
chat: "Halo! Iya, aku baru aja baca. Kayaknya kita harus revisi bagian akhir deh.",
time: "2025-07-24T09:02:03Z",
},
{
nama: "Dina",
role: 1,
chat: "Setuju. Aku juga kurang sreg sama penutupnya.",
time: "2025-07-24T09:02:45Z",
},
{
nama: "Rafi",
role: 2,
chat: "Oke, aku coba edit malam ini ya. Nanti aku share ulang versinya.",
time: "2025-07-24T09:03:10Z",
},
{
nama: "Dina",
role: 1,
chat: "Siap, makasih ya. Jangan begadang!",
time: "2025-07-24T09:03:30Z",
},
];
const styles = StyleSheet.create({
container: {
paddingVertical: 10,
paddingHorizontal: 12,
},
messageRow: {
flexDirection: "row",
marginBottom: 12,
},
rightAlign: {
justifyContent: "flex-end",
},
leftAlign: {
justifyContent: "flex-start",
},
bubble: {
maxWidth: "75%",
padding: 10,
borderRadius: 12,
},
bubbleRight: {
backgroundColor: "#DCF8C6", // hijau muda
borderTopRightRadius: 0,
},
bubbleLeft: {
backgroundColor: "#F0F0F0", // abu-abu terang
borderTopLeftRadius: 0,
},
sender: {
fontSize: 12,
fontWeight: "bold",
marginBottom: 2,
color: "#555",
},
message: {
fontSize: 15,
color: "#000",
},
time: {
fontSize: 10,
color: "#888",
textAlign: "right",
marginTop: 4,
},
});

View File

@@ -0,0 +1,80 @@
import {
AlertDefaultSystem,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationCreatePartisipasi } from "@/service/api-client/api-collaboration";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function CollaborationCreatePartisipans() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [description, setDescription] = useState("");
const [isLoading, setLoading] = useState(false);
const handlerSubmitParticipans = async () => {
try {
setLoading(true);
const response = await apiCollaborationCreatePartisipasi({
id: id as string,
data: {
authorId: user?.id,
description,
},
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil disimpan",
});
router.replace(`/collaboration/${id}/list-of-participants`);
} else {
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<ViewWrapper>
<TextAreaCustom
// label="Deskripsi"
placeholder="Masukan deskripsi diri anda .."
value={description}
onChangeText={setDescription}
required
showCount
maxLength={1000}
/>
<ButtonCustom
disabled={description.length === 0}
isLoading={isLoading}
onPress={() => {
AlertDefaultSystem({
title: "Simpan data deskripsi",
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal",
textRight: "Simpan",
onPressRight: () => {
handlerSubmitParticipans();
},
});
}}
>
Simpan
</ButtonCustom>
</ViewWrapper>
);
}

View File

@@ -1,26 +1,54 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, BackButton,
BaseBox, DotButton,
DrawerCustom, DrawerCustom,
StackCustom, MenuDrawerDynamicGrid,
TextCustom, ViewWrapper
ViewWrapper,
} from "@/components"; } from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { MaterialIcons } from "@expo/vector-icons"; import { apiCollaborationGetOne } from "@/service/api-client/api-collaboration";
import { useLocalSearchParams } from "expo-router"; import { Ionicons } from "@expo/vector-icons";
import { useState } from "react"; import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailParticipant() { export default function CollaborationDetailParticipant() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerParticipant(true)} />
),
}}
/>
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> <Collaboration_BoxDetailSection data={data} />
<BaseBox style={{ height: 500 }}> {/* <BaseBox style={{ height: 500 }}>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Partisipan Partisipan
</TextCustom> </TextCustom>
@@ -39,13 +67,33 @@ export default function CollaborationDetailParticipant() {
} }
/> />
))} ))}
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawerParticipant} isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)} closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"} height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <Ionicons name="people" size={24} color="white" />,
label: "Daftar Partisipan",
path: `/collaboration/${id}/list-of-participants`,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawerParticipant(false);
}}
/>
</DrawerCustom>
{/* <DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
> >
<StackCustom> <StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom> <TextCustom bold>Deskripsi Diri</TextCustom>
@@ -56,7 +104,7 @@ export default function CollaborationDetailParticipant() {
Temporibus iusto soluta necessitatibus. Temporibus iusto soluta necessitatibus.
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</DrawerCustom> </DrawerCustom> */}
</> </>
); );
} }

View File

@@ -1,30 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton, BackButton,
ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom,
TextCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection"; import {
import { router, Stack, useLocalSearchParams } from "expo-router"; apiCollaborationGetOne
import { useState } from "react"; } from "@/service/api-client/api-collaboration";
import { MaterialIcons } from "@expo/vector-icons";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetailProjectMain() { export default function CollaborationDetailProjectMain() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [data, setData] = useState<any>();
const [selected, setSelected] = useState<(string | number)[]>([]); const [loadingGetData, setLoadingGetData] = useState(false);
const handleEdit = () => { useFocusEffect(
console.log("Edit collaboration"); useCallback(() => {
router.push("/(application)/(user)/collaboration/(id)/edit"); handlerLoadData();
}, [id])
);
const handlerLoadData = async () => {
try {
setLoadingGetData(true);
await onLoadData();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleSubmit = (item: any) => {
console.log("item :", item);
router.push(item.path);
setOpenDrawer(false);
}; };
return ( return (
@@ -37,34 +72,21 @@ export default function CollaborationDetailProjectMain() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> {loadingGetData ? (
<Collaboration_MainParticipanSelectedSection <LoaderCustom />
) : (
<>
<Collaboration_BoxDetailSection data={data} />
{/* <Collaboration_MainParticipanSelectedSection
selected={selected} selected={selected}
setSelected={setSelected} setSelected={setSelected}
setOpenDrawerParticipant={setOpenDrawerParticipant} setOpenDrawerParticipant={setOpenDrawerParticipant}
/> listData={listData as any}
/> */}
<ButtonCustom
onPress={() => {
AlertDefaultSystem({
title: "Buat Grup",
message:
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => {
router.navigate(
"/(application)/(user)/collaboration/(tabs)/group"
);
console.log("selected :", selected);
},
});
}}
>
Buat Grup
</ButtonCustom>
<Spacing /> <Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -76,31 +98,20 @@ export default function CollaborationDetailProjectMain() {
data={[ data={[
{ {
label: "Edit", label: "Edit",
path: "/(application)/(user)/collaboration/(tabs)/group", path: `/(application)/(user)/collaboration/${id}/edit`,
icon: <IconEdit />, icon: <IconEdit />,
}, },
{
label: "Pilih Partisipan",
path: `/(application)/(user)/collaboration/${id}/select-of-participants`,
icon: <MaterialIcons name="checklist" size={24} color="white" />,
},
]} ]}
onPressItem={(item) => { onPressItem={(item: any) => {
handleEdit(); handleSubmit(item);
}} }}
/> />
</DrawerCustom> </DrawerCustom>
<DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
>
<StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom>
<TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Commodi,
itaque adipisci. Voluptas, sed quod! Ad facere labore voluptates,
neque quidem aut reprehenderit ducimus mollitia quisquam temporibus!
Temporibus iusto soluta necessitatibus.
</TextCustom>
</StackCustom>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -1,27 +1,140 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
SelectCustom, SelectCustom,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import {
apiCollaborationEditData,
apiCollaborationGetOne,
} from "@/service/api-client/api-collaboration";
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function CollaborationEdit() { export default function CollaborationEdit() {
const { id } = useLocalSearchParams();
console.log("id :", id);
const [data, setData] = useState<any>();
const [listMaster, setListMaster] = useState<any[]>([]);
const [loadingData, setLoadingData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useFocusEffect(
useCallback(() => {
const fetchData = async () => {
try {
setLoadingData(true);
await onLoadData();
await onLoadMaster();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingData(false);
}
};
fetchData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
async function onLoadMaster() {
try {
const response = await apiMasterCollaborationType();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
}
const handlerSubmitUpdate = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.projectCollaborationMaster_IndustriId ||
!data?.purpose ||
!data?.benefit
) {
Toast.show({
type: "error",
text1: "Gagal",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
const response = await apiCollaborationEditData({
id: id as string,
data: data,
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "error",
text1: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{loadingData ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required /> <TextInputCustom
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required /> label="Judul"
placeholder="Masukan judul"
required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukan lokasi"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/>
<SelectCustom <SelectCustom
label="Pilih Industri" label="Pilih Industri"
data={[ data={listMaster?.map((item: any) => ({
{ label: "Industri 1", value: "industri-1" }, label: item.name,
{ label: "Industri 2", value: "industri-2" }, value: item.id,
{ label: "Industri 3", value: "industri-3" }, }))}
]} value={data?.projectCollaborationMaster_IndustriId}
onChange={(value) => console.log(value)} onChange={(value) =>
setData({ ...data, projectCollaborationMaster_IndustriId: value })
}
/> />
<TextAreaCustom <TextAreaCustom
@@ -30,6 +143,8 @@ export default function CollaborationEdit() {
placeholder="Masukan tujuan proyek" placeholder="Masukan tujuan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.purpose}
onChangeText={(value) => setData({ ...data, purpose: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -38,16 +153,19 @@ export default function CollaborationEdit() {
placeholder="Masukan keuntungan proyek" placeholder="Masukan keuntungan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.benefit}
onChangeText={(value) => setData({ ...data, benefit: value })}
/> />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
title="Update" title="Update"
onPress={() => { onPress={() => {
console.log("Update proyek"); handlerSubmitUpdate();
router.back();
}} }}
/> />
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,22 +1,75 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton, BackButton,
ButtonCustom, ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
InformationBox,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import {
apiCollaborationGetOne,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function CollaborationDetail() { export default function CollaborationDetail() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawerPartisipasi, setOpenDrawerPartisipasi] = useState(false); const [data, setData] = useState<any>();
const [openDrawerMenu, setOpenDrawerMenu] = useState(false); const [openDrawerMenu, setOpenDrawerMenu] = useState(false);
const [isParticipant, setIsParticipant] = useState(false);
const [loadingIsParticipant, setLoadingIsParticipant] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadParticipants();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiCollaborationGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadParticipants = async () => {
try {
setLoadingIsParticipant(true);
const response = await apiCollaborationGetParticipants({
category: "check-participant",
id: id as string,
authorId: user?.id,
});
if (response.success) {
setIsParticipant(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingIsParticipant(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -29,15 +82,35 @@ export default function CollaborationDetail() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> {!data && !isParticipant ? (
<LoaderCustom />
<ButtonCustom onPress={() => setOpenDrawerPartisipasi(true)}> ) : (
Partisipasi <>
{user?.id === data?.Author?.id && (
<InformationBox
text={
"Tombol partisipasi hanya muncul pada proyek milik orang lain"
}
/>
)}
<Collaboration_BoxDetailSection data={data} />
{user?.id !== data?.Author?.id && (
<ButtonCustom
disabled={isParticipant || loadingIsParticipant}
onPress={() => {
router.push(`/collaboration/${id}/create-pacticipants`);
// setOpenDrawerPartisipasi(true);
}}
>
{isParticipant ? "Anda telah berpartisipasi" : "Partisipasi"}
</ButtonCustom> </ButtonCustom>
)}
</>
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer Partisipasi */} {/* Drawer Partisipasi */}
<DrawerCustom {/* <DrawerCustom
isVisible={openDrawerPartisipasi} isVisible={openDrawerPartisipasi}
closeDrawer={() => setOpenDrawerPartisipasi(false)} closeDrawer={() => setOpenDrawerPartisipasi(false)}
height={300} height={300}
@@ -48,6 +121,8 @@ export default function CollaborationDetail() {
required required
showCount showCount
maxLength={500} maxLength={500}
value={description}
onChangeText={setDescription}
/> />
<ButtonCustom <ButtonCustom
@@ -58,19 +133,21 @@ export default function CollaborationDetail() {
message: "Apakah anda sudah yakin ingin menyimpan data ini ?", message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Simpan", textRight: "Simpan",
onPressRight: () => router.replace(`/collaboration/(tabs)/group`), onPressRight: () => {
handlerSubmitParticipans();
},
}); });
}} }}
> >
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</DrawerCustom> </DrawerCustom> */}
{/* Drawer Menu */} {/* Drawer Menu */}
<DrawerCustom <DrawerCustom
isVisible={openDrawerMenu} isVisible={openDrawerMenu}
closeDrawer={() => setOpenDrawerMenu(false)} closeDrawer={() => setOpenDrawerMenu(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={[ data={[

View File

@@ -1,38 +1,79 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { apiCollaborationGetParticipants } from "@/service/api-client/api-collaboration";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView } from "react-native"; import { ScrollView } from "react-native";
export default function CollaborationListOfParticipants() { export default function CollaborationListOfParticipants() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
const [description, setDescription] = useState("");
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetParticipants({
category: "list",
id: id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada partisipan</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<BaseBox key={index} paddingBlock={5}> <BaseBox key={index} paddingBlock={5}>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatarHref={`/profile/${id}`} avatar={item?.User?.Profile?.imageId}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
name={item?.User?.username}
rightComponent={ rightComponent={
<Feather <Feather
name="chevron-right" name="chevron-right"
size={24} size={24}
color="white" color="white"
onPress={() => setOpenDrawer(true)} onPress={() => {
setDescription(item?.deskripsi_diri);
setOpenDrawer(true);
}}
/> />
} }
/> />
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer */} {/* Drawer */}
@@ -44,34 +85,7 @@ export default function CollaborationListOfParticipants() {
<TextCustom bold>Deskripsi diri</TextCustom> <TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox> <BaseBox>
<ScrollView style={{ height: "80%" }}> <ScrollView style={{ height: "80%" }}>
<TextCustom> <TextCustom>{description}</TextCustom>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut iqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
</TextCustom>
</ScrollView> </ScrollView>
</BaseBox> </BaseBox>
<Spacing /> <Spacing />

View File

@@ -0,0 +1,251 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
AvatarComp,
BaseBox,
BoxButtonOnFooter,
ButtonCustom,
CheckboxCustom,
CheckboxGroup,
DrawerCustom,
Grid,
LoaderCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import ModalCustom from "@/components/Modal/ModalCustom";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import {
apiCollaborationCreateGroup,
apiCollaborationGetParticipants,
} from "@/service/api-client/api-collaboration";
import { MaterialIcons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { ScrollView, View } from "react-native";
import Toast from "react-native-toast-message";
export default function CollaborationSelectOfParticipants() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [listData, setListData] = useState<any[]>();
const [loadingGetData, setLoadingGetData] = useState(false);
const [description, setDescription] = useState("");
const [selected, setSelected] = useState<(string | number)[]>([]);
const [openDrawer, setOpenDrawer] = useState(false);
const [nameGroup, setNameGroup] = useState("");
const [openModal, setOpenModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiCollaborationGetParticipants({
category: "list",
id: id as string,
});
// console.log("response :", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlerCreateGroup = async () => {
if (_.isEmpty(nameGroup)) {
Toast.show({
type: "error",
text1: "Nama grup tidak boleh kosong",
});
return;
}
try {
setIsLoading(true);
const response = await apiCollaborationCreateGroup({
id: id as string,
data: {
authorId: user?.id,
nameGroup: nameGroup,
listSelect: selected,
},
});
if (response.success) {
Toast.show({
type: "success",
text1: "Grup berhasil dibuat",
});
router.push("/(application)/(user)/collaboration/(tabs)/group");
} else {
Toast.show({
type: "error",
text1: "Gagal membuat grup",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const handlerSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
disabled={_.isEmpty(selected)}
onPress={() => {
setOpenModal(true);
setNameGroup("");
}}
>
Buat Grup
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<>
<ViewWrapper
footerComponent={_.isEmpty(listData) ? null : handlerSubmit()}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada partisipan</TextCustom>
) : (
<StackCustom>
<TextCustom size="default" color="red" bold>
*{" "}
<TextCustom size="small" semiBold>
Pilih user yang akan menjadi tim proyek anda
</TextCustom>
</TextCustom>
<CheckboxGroup
value={selected}
onChange={(val: any) => {
console.log("val :", val);
setSelected(val);
}}
>
{listData?.map((item: any, index: any) => (
<View key={index}>
<Grid key={index}>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<CheckboxCustom valueKey={item?.User?.id} />
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<AvatarComp
size="base"
fileId={item?.User?.Profile?.imageId}
/>
</Grid.Col>
<Grid.Col span={6} style={{ justifyContent: "center" }}>
<TextCustom bold truncate>
{item?.User?.username}
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<MaterialIcons
name="notes"
size={ICON_SIZE_SMALL}
color="white"
onPress={() => {
setOpenDrawer(true);
setDescription(item?.deskripsi_diri);
}}
/>
</Grid.Col>
</Grid>
</View>
))}
</CheckboxGroup>
</StackCustom>
)}
</ViewWrapper>
<ModalCustom isVisible={openModal}>
<StackCustom gap={0}>
<TextAreaCustom
placeholder="Nama Grup"
value={nameGroup}
onChangeText={(val) => setNameGroup(val)}
/>
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<ButtonCustom
backgroundColor="gray"
onPress={() => {
setOpenModal(false);
}}
>
Batal
</ButtonCustom>
</Grid.Col>
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
<ButtonCustom
isLoading={isLoading}
disabled={_.isEmpty(nameGroup)}
onPress={() => {
AlertDefaultSystem({
title: "Buat Grup",
message:
"Apakah anda yakin ingin membuat grup untuk proyek ini ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => {
handlerCreateGroup();
},
});
}}
>
Simpan
</ButtonCustom>
</Grid.Col>
</Grid>
</StackCustom>
</ModalCustom>
{/* Drawer */}
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<StackCustom>
<TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox>
<ScrollView style={{ height: "80%" }}>
<TextCustom>{description}</TextCustom>
</ScrollView>
</BaseBox>
<Spacing />
</StackCustom>
</DrawerCustom>
</>
);
}

View File

@@ -1,27 +1,145 @@
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
SelectCustom, SelectCustom,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiCollaborationCreate } from "@/service/api-client/api-collaboration";
import { apiMasterCollaborationType } from "@/service/api-client/api-master";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
interface CollaborationCreateProps {
title?: string;
lokasi?: string;
purpose?: string;
benefit?: string;
projectCollaborationMaster_IndustriId?: string;
userId?: string;
}
export default function CollaborationCreate() { export default function CollaborationCreate() {
const { user } = useAuth();
const [listMaster, setListMaster] = useState<any>([]);
const [loadingMaster, setLoadingMaster] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = React.useState<CollaborationCreateProps>({
title: "",
lokasi: "",
purpose: "",
benefit: "",
projectCollaborationMaster_IndustriId: "",
userId: "",
});
useEffect(() => {
onLoadMaster();
}, []);
async function onLoadMaster() {
try {
setLoadingMaster(true);
const response = await apiMasterCollaborationType();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
}
const handlerSubmit = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.purpose ||
!data?.benefit ||
!data?.projectCollaborationMaster_IndustriId
) {
Toast.show({
type: "error",
text1: "Gagal",
text2: "Harap isi semua data",
});
return;
}
const newData: CollaborationCreateProps = {
title: data?.title,
lokasi: data?.lokasi,
purpose: data?.purpose,
benefit: data?.benefit,
projectCollaborationMaster_IndustriId:
data?.projectCollaborationMaster_IndustriId,
userId: user?.id,
};
try {
setIsLoading(true);
const response = await apiCollaborationCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: response.message,
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal",
text2: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{loadingMaster ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required /> <TextInputCustom
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required /> label="Judul"
placeholder="Masukan judul"
required
value={data?.title}
onChangeText={(value: any) => setData({ ...data, title: value })}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukan lokasi"
required
value={data?.lokasi}
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/>
<SelectCustom <SelectCustom
label="Pilih Industri" label="Pilih Industri"
data={[ data={listMaster?.map((item: any) => ({
{ label: "Industri 1", value: "industri-1" }, label: item.name,
{ label: "Industri 2", value: "industri-2" }, value: item.id,
{ label: "Industri 3", value: "industri-3" }, }))}
]} value={data?.projectCollaborationMaster_IndustriId}
onChange={(value) => console.log(value)} onChange={(value: any) => {
console.log(value);
setData({
...data,
projectCollaborationMaster_IndustriId: value,
});
}}
/> />
<TextAreaCustom <TextAreaCustom
@@ -30,6 +148,8 @@ export default function CollaborationCreate() {
placeholder="Masukan tujuan proyek" placeholder="Masukan tujuan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.purpose}
onChangeText={(value: any) => setData({ ...data, purpose: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -38,16 +158,17 @@ export default function CollaborationCreate() {
placeholder="Masukan keuntungan proyek" placeholder="Masukan keuntungan proyek"
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.benefit}
onChangeText={(value: any) => setData({ ...data, benefit: value })}
/> />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
title="Simpan" title="Simpan"
onPress={() => { onPress={() => handlerSubmit()}
console.log("Simpan proyek");
router.back();
}}
/> />
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,11 +1,41 @@
import { import {
FloatingButton, FloatingButton,
ViewWrapper LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
import { router } from "expo-router"; import { apiDonationGetAll } from "@/service/api-client/api-donation";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationBeranda() { export default function DonationBeranda() {
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiDonationGetAll({
category: "beranda"
});
console.log("[RES GET ALL]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
@@ -13,9 +43,15 @@ export default function DonationBeranda() {
<FloatingButton onPress={() => router.push("/donation/create")} /> <FloatingButton onPress={() => router.push("/donation/create")} />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Donation_BoxPublish key={index} id={index.toString()}/> <LoaderCustom />
))} ) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">Belum ada donasi</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Donation_BoxPublish data={item} key={index} id={item.id} />
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,76 +1,142 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
DummyLandscapeImage, DummyLandscapeImage,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { useAuth } from "@/hooks/use-auth";
import { router } from "expo-router"; import { apiDonationGetAll } from "@/service/api-client/api-donation";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Href, router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function DonationMyDonation() { export default function DonationMyDonation() {
const randomStatusData = Array.from({ length: 10 }, () => { const { user } = useAuth();
const randomIndex = Math.floor( const [list, setList] = useState<any[] | null>(null);
Math.random() * dummyMasterStatusTransaction.length const [loadList, setLoadList] = useState(false);
);
return dummyMasterStatusTransaction[randomIndex];
});
const handlePress = (value: string) => { useFocusEffect(
if (value === "menunggu") { useCallback(() => {
router.push(`/donation/${value}/(transaction-flow)/123/invoice`); onLoadData();
} else if (value === "proses") { }, [user?.id])
router.push(`/donation/${value}/(transaction-flow)/123/process`); );
} else if (value === "berhasil") {
router.push(`/donation/${value}/(transaction-flow)/123/success`); const onLoadData = async () => {
} else if (value === "gagal") { try {
router.push(`/donation/${value}/(transaction-flow)/123/failed`); setLoadList(true);
const response = await apiDonationGetAll({
category: "my-donation",
authorId: user?.id,
});
console.log(
"[RES GET MY DONATION]",
JSON.stringify(response.data, null, 2)
);
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const handlerColor = (status: string) => {
if (status === "menunggu") {
return "orange";
} else if (status === "proses") {
return "white";
} else if (status === "berhasil") {
return "green";
} else if (status === "gagal") {
return "red";
}
};
const handlePress = ({
invoiceId,
donationId,
status,
}: {
invoiceId: string;
donationId: string;
status: string;
}) => {
const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`;
if (status === "menunggu") {
router.push(`${url}/invoice`);
} else if (status === "proses") {
router.push(`${url}/process`);
} else if (status === "berhasil") {
router.push(`${url}/success`);
} else if (status === "gagal") {
router.push(`${url}/failed`);
} }
}; };
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{randomStatusData.map((item, index) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Belum ada transaksi
</TextCustom>
) : (
list?.map((item, index) => (
<BaseBox <BaseBox
key={index} key={index}
paddingTop={7} paddingTop={7}
paddingBottom={7} paddingBottom={7}
onPress={() => { onPress={() => {
handlePress(item.value); handlePress({
status: _.lowerCase(item.statusInvoice),
invoiceId: item.id,
donationId: item.donasiId,
});
}} }}
> >
<Grid> <Grid>
<Grid.Col span={5}> <Grid.Col span={5}>
<DummyLandscapeImage height={100} unClickPath /> <DummyLandscapeImage
height={100}
unClickPath
imageId={item.imageId}
/>
</Grid.Col> </Grid.Col>
<Grid.Col span={1}> <Grid.Col span={1}>
<View /> <View />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<StackCustom gap={"sm"}> <StackCustom>
<View> <TextCustom truncate={2} bold>
<TextCustom truncate> {item.title || "-"}
Judul Donasi: Lorem ipsum dolor sit amet consectetur
adipisicing elit.
</TextCustom> </TextCustom>
</View>
<View>
<TextCustom>Donasi Saya</TextCustom>
<TextCustom bold color="yellow"> <TextCustom bold color="yellow">
Rp. 7.500.000 Rp. {formatCurrencyDisplay(item.nominal)}
</TextCustom> </TextCustom>
</View>
<BadgeCustom variant="light" color={item.color} fullWidth> <BadgeCustom
{item.label} variant="light"
color={handlerColor(_.lowerCase(item.statusInvoice))}
fullWidth
>
{item.statusInvoice}
</BadgeCustom> </BadgeCustom>
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,12 +1,48 @@
import { ScrollableCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Donasi_BoxStatus from "@/screens/Donation/BoxStatus"; import Donasi_BoxStatus from "@/screens/Donation/BoxStatus";
import { useState } from "react"; import { apiDonationGetByStatus } from "@/service/api-client/api-donation";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationStatus() { export default function DonationStatus() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
const [listData, setListData] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [activeCategory])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
setListData(null);
} finally {
setLoadList(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -26,13 +62,19 @@ export default function DonationStatus() {
); );
return ( return (
<ViewWrapper hideFooter headerComponent={scrollComponent}> <ViewWrapper hideFooter headerComponent={scrollComponent}>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData?.map((item: any, index: number) => (
<Donasi_BoxStatus <Donasi_BoxStatus
key={index} key={index}
id={index.toString()} data={item}
status={activeCategory as string} status={activeCategory as string}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
@@ -9,17 +10,120 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import {
apiDonationGetNewsById,
apiDonationUpdateNews,
} from "@/service/api-client/api-donation";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationEditNews() { export default function DonationEditNews() {
const { news } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [image, setImage] = useState<IFileData | null>(null);
const [isLoading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [news])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetNewsById({
id: news as string,
category: "get-one",
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmitUpdate = async () => {
let newData;
if (!data.title || !data.deskripsi) {
Toast.show({
type: "error",
text1: "Judul dan deskripsi harus diisi",
});
return;
}
try {
setLoading(true);
newData = {
title: data?.title,
deskripsi: data?.deskripsi,
};
if (image && image?.uri) {
const uploadNewImage = await uploadFileService({
dirId: DIRECTORY_ID.donasi_kabar,
imageUri: image?.uri,
});
newData = {
title: data?.title,
deskripsi: data?.deskripsi,
newImageId: uploadNewImage.data.id,
};
}
const response = await apiDonationUpdateNews({
id: news as string,
data: newData,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mengupdate berita",
});
return;
}
Toast.show({
type: "success",
text1: "Berita berhasil diperbarui",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." /> <InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={
image
? image.uri
: data && data.imageId
? API_STRORAGE.GET({ fileId: data.imageId })
: undefined
}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -30,6 +134,8 @@ export default function DonationEditNews() {
label="Judul Berita" label="Judul Berita"
placeholder="Masukan judul berita" placeholder="Masukan judul berita"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Berita" label="Deskripsi Berita"
@@ -37,12 +143,16 @@ export default function DonationEditNews() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
disabled={!data?.title || !data?.deskripsi}
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
@@ -12,13 +13,45 @@ import {
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { IconTrash } from "@/components/_Icon/IconTrash"; import { IconTrash } from "@/components/_Icon/IconTrash";
import dayjs from "dayjs"; import { useAuth } from "@/hooks/use-auth";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiDonationDeleteNews,
apiDonationGetNewsById,
} from "@/service/api-client/api-donation";
import { formatChatTime } from "@/utils/formatChatTime";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationNews() { export default function DonationNews() {
const { id, news } = useLocalSearchParams(); const { user } = useAuth();
const { news } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [news])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetNewsById({
id: news as string,
category: "get-one",
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
@@ -26,28 +59,28 @@ export default function DonationNews() {
options={{ options={{
title: "Detail Kabar", title: "Detail Kabar",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, headerRight: () =>
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
),
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom style={{ alignSelf: "flex-end" }}> <TextCustom style={{ alignSelf: "flex-end" }}>
{dayjs().format("DD MMM YYYY")} {formatChatTime(data?.createdAt)}
</TextCustom> </TextCustom>
<DummyLandscapeImage /> {data && data.imageId && (
<DummyLandscapeImage imageId={data.imageId} />
)}
<TextCustom bold size="large" align="center"> <TextCustom bold size="large" align="center">
Judul Berita {data?.title || "-"}
</TextCustom> </TextCustom>
<TextCustom> <TextCustom>{data?.deskripsi || "-"}</TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente
est id temporibus perferendis eos reiciendis reprehenderit tempora
ut quibusdam dolores facilis rerum exercitationem recusandae quis
neque, adipisci dolorum, aspernatur labore?
</TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</ViewWrapper> </ViewWrapper>
@@ -62,12 +95,12 @@ export default function DonationNews() {
{ {
icon: <IconEdit />, icon: <IconEdit />,
label: "Edit Berita", label: "Edit Berita",
path: `/donation/${id}/(news)/${news}/edit-news` as any, path: `/donation/[id]/(news)/${news}/edit-news` as any,
}, },
{ {
icon: <IconTrash />, icon: <IconTrash />,
label: "Hapus Berita", label: "Hapus Berita",
path: `` as any, path: "",
color: "red", color: "red",
}, },
]} ]}
@@ -79,7 +112,23 @@ export default function DonationNews() {
message: "Apakah Anda yakin ingin menghapus berita ini?", message: "Apakah Anda yakin ingin menghapus berita ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: async () => {
const response = await apiDonationDeleteNews({
id: news as string,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal menghapus berita",
});
return;
}
Toast.show({
type: "success",
text1: "Berita berhasil dihapus",
});
router.back(); router.back();
}, },
}); });

View File

@@ -9,17 +9,79 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiDonationCreateNews } from "@/service/api-client/api-donation";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationAddNews() { export default function DonationAddNews() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
title: "",
deskripsi: "",
});
const [image, setImage] = useState<IFileData | null>(null);
const [isLoading, setLoading] = useState(false);
const handlerSubmit = async () => {
let newData: any = { ...data };
try {
setLoading(true);
if (image) {
const responseUploadImage = await uploadFileService({
dirId: DIRECTORY_ID.donasi_kabar,
imageUri: image?.uri,
});
newData = {
...newData,
imageId: responseUploadImage.data.id,
};
}
const response = await apiDonationCreateNews({
id: id as string,
data: newData,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal menambah berita",
});
return
}
Toast.show({
type: "success",
text1: "Berita berhasil ditambahkan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." /> <InformationBox text="Upload gambar bersifat opsional untuk melengkapi kabar terkait donasi Anda." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -30,6 +92,13 @@ export default function DonationAddNews() {
label="Judul Berita" label="Judul Berita"
placeholder="Masukan judul berita" placeholder="Masukan judul berita"
required required
value={data.title}
onChangeText={(value) => {
setData({
...data,
title: value,
});
}}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Berita" label="Deskripsi Berita"
@@ -37,12 +106,21 @@ export default function DonationAddNews() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => {
setData({
...data,
deskripsi: value,
});
}}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
disabled={!data.title || !data.deskripsi}
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmit();
}} }}
> >
Simpan Simpan

View File

@@ -1,45 +1,88 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
Grid, Grid,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom, TextCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import dayjs from "dayjs"; import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { formatChatTime } from "@/utils/formatChatTime";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationRecapOfNews() { export default function DonationRecapOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetNewsById({
id: id as string,
category: "get-all",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Daftar Kabar", title: "Daftar Kabar",
headerLeft: () => <BackButton />, }} headerLeft: () => <BackButton />,
}}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}> <LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada kabar
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
<Grid> <Grid>
<Grid.Col span={8}> <Grid.Col span={8}>
<TextCustom truncate bold> <TextCustom truncate bold>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. {item?.title || "-"}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> <Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom size="small"> <TextCustom size="small">
{dayjs().format("DD MMM YYYY")} {formatChatTime(item?.createdAt)}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -1,21 +1,55 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
Grid, Grid,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import dayjs from "dayjs"; import { apiDonationGetNewsById } from "@/service/api-client/api-donation";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { formatChatTime } from "@/utils/formatChatTime";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function DonationRecapOfNews() { export default function DonationRecapOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiDonationGetNewsById({
id: id as string,
category: "get-all",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
@@ -27,20 +61,30 @@ export default function DonationRecapOfNews() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<BaseBox key={index} href={`/donation/${id}/(news)/${index}`}> <LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada kabar
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox key={index} href={`/donation/[id]/(news)/${item.id}`}>
<Grid> <Grid>
<Grid.Col span={8}> <Grid.Col span={8}>
<TextCustom truncate bold> <TextCustom truncate bold>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. {item?.title || "-"}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> <Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom size="small">{dayjs().format("DD MMM YYYY")}</TextCustom> <TextCustom size="small">
{formatChatTime(item?.createdAt)}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -0,0 +1,222 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
Grid,
InformationBox,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import CopyButton from "@/components/Button/CoyButton";
import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
import {
apiDonationGetInvoiceById,
apiDonationUpdateInvoice,
} from "@/service/api-client/api-donation";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function DonationInvoice() {
const { invoiceId } = useLocalSearchParams();
console.log("invoiceId", invoiceId);
const [data, setData] = useState<any>(null);
const [image, setImage] = useState<any>(null);
const [isLoading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [invoiceId])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetInvoiceById({
id: invoiceId as string,
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerUpdateInvoice = async () => {
try {
setLoading(true);
const responseUploadImage = await uploadFileService({
dirId: DIRECTORY_ID.donasi_bukti_transfer,
imageUri: image?.uri,
});
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
if (!responseUploadImage?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah bukti transfer",
});
return;
}
const fileId = responseUploadImage?.data?.id;
const response = await apiDonationUpdateInvoice({
id: invoiceId as string,
fileId: fileId,
status: "proses",
});
console.log("[RESPONSE UPDATE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah bukti transfer",
});
return;
}
Toast.show({
type: "success",
text1: "Berhasil mengunggah bukti transfer",
});
router.replace(`/donation/[id]/(transaction-flow)/${invoiceId}/process`);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return (
<>
<ViewWrapper>
<StackCustom>
<InformationBox
text={`Mohon transfer donasi anda ke rekening dibawah`}
/>
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom bold>
BANK: {data?.DonasiMaster_Bank?.name}
</TextCustom>
{/* <TextCustom>{data?.DonasiMaster_Bank?.accountName}</TextCustom> */}
<Spacing height={10} />
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
{data?.DonasiMaster_Bank?.norek}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.DonasiMaster_Bank?.norek} />
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom>Jumlah Transaksi</TextCustom>
<Spacing height={10} />
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
Rp. {formatCurrencyDisplay(data?.nominal) || "-"}
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<CopyButton textToCopy={data?.nominal} />
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom>
<TextCustom bold align="center" size={"small"} color="gray">
Upload bukti transfer anda.
</TextCustom>
{image ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 10,
paddingInline: 20,
}}
>
<TextCustom bold align="center" truncate>
{image?.name}
</TextCustom>
</View>
) : null}
<ButtonCenteredOnly
onPress={() => {
pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
<ButtonCustom
disabled={!image}
isLoading={isLoading}
onPress={() => {
handlerUpdateInvoice();
}}
>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</>
);
}

View File

@@ -1,13 +1,6 @@
import { import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
BaseBox, import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
Grid, import { View } from "react-native";
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { ActivityIndicator } from "react-native";
export default function DonationProcess() { export default function DonationProcess() {
return ( return (
@@ -16,13 +9,16 @@ export default function DonationProcess() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom align="center" bold> <TextCustom align="center" bold>
Admin sedang memproses transaksi donasimu Admin sedang memvalidasi data dan bukti transfer anda. Mohon
tunggu proses ini selesai.
</TextCustom> </TextCustom>
<ActivityIndicator size="large" color={MainColor.yellow} /> <View style={{ alignItems: "center", justifyContent: "center" }}>
<MoneyTransferAnimation />
</View>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<BaseBox> {/* <BaseBox>
<Grid> <Grid>
<Grid.Col span={10} style={{ justifyContent: "center" }}> <Grid.Col span={10} style={{ justifyContent: "center" }}>
<TextCustom size="small"> <TextCustom size="small">
@@ -38,7 +34,7 @@ export default function DonationProcess() {
/> />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,110 +0,0 @@
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
Grid,
InformationBox,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { router, useLocalSearchParams } from "expo-router";
export default function DonationInvoice() {
const { id, transaction } = useLocalSearchParams();
return (
<>
<ViewWrapper>
<StackCustom>
<InformationBox text={`Mohon transfer donasi anda ke rekening dibawah dengan Id: ${transaction}`} />
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom>Nama BANK</TextCustom>
<TextCustom>Nama Penerima</TextCustom>
<Spacing height={10} />
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
4567898765433567
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<ButtonCustom>Salin</ButtonCustom>
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom gap={"xs"}>
<TextCustom>Jumlah Transaksi</TextCustom>
<Spacing height={10} />
<BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}>
<Grid.Col
span={8}
style={{
justifyContent: "center",
}}
>
<TextCustom size="xlarge" bold color="yellow">
Rp. 1.000.000
</TextCustom>
</Grid.Col>
<Grid.Col
span={4}
style={{
alignItems: "flex-end",
}}
>
<ButtonCustom>Salin</ButtonCustom>
</Grid.Col>
</Grid>
</BaseBox>
</StackCustom>
</BaseBox>
<BaseBox>
<StackCustom>
<TextCustom>Upload bukti transfer anda.</TextCustom>
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</BaseBox>
<ButtonCustom
onPress={() => {
router.push(`/donation/${id}/(transaction-flow)/process`);
}}
>
Saya Sudah Transfer
</ButtonCustom>
</StackCustom>
<Spacing />
</ViewWrapper>
</>
);
}

View File

@@ -9,15 +9,43 @@ import {
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function InvestmentInputDonation() { export default function InvestmentInputDonation() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [nominal, setNominal] = useState<number>(0);
const handlerSubmit = async () => {
try {
await AsyncStorage.setItem(
LOCAL_STORAGE_KEY.transactionDonation,
JSON.stringify({ nominal: nominal.toString() })
);
router.replace(`/donation/${id}/select-bank`);
} catch (error) {
console.log("[ERROR]", error);
}
};
const displayJumlah = formatCurrencyDisplay(nominal);
const handleChangeCurrency = (text: string) => {
const numeric = text.replace(/\D/g, "");
setNominal(Number(numeric));
};
const bottomComponent = ( const bottomComponent = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => router.replace(`/donation/${id}/select-bank`)} disabled={nominal < 10000 || nominal === 0}
onPress={() => {
handlerSubmit();
}}
> >
Lanjutan Lanjutan
</ButtonCustom> </ButtonCustom>
@@ -27,7 +55,7 @@ export default function InvestmentInputDonation() {
<> <>
<ViewWrapper footerComponent={bottomComponent}> <ViewWrapper footerComponent={bottomComponent}>
{listData.map((item, i) => ( {listData.map((item, i) => (
<BaseBox key={i}> <BaseBox key={i} onPress={() => setNominal(item.value)}>
<Grid> <Grid>
<Grid.Col span={8}> <Grid.Col span={8}>
<TextCustom bold size="large"> <TextCustom bold size="large">
@@ -48,9 +76,12 @@ export default function InvestmentInputDonation() {
<BaseBox> <BaseBox>
<TextInputCustom <TextInputCustom
keyboardType="numeric"
label="Nominal lainnya" label="Nominal lainnya"
placeholder="0" placeholder="0"
iconLeft="Rp." iconLeft="Rp."
value={displayJumlah}
onChangeText={(value) => handleChangeCurrency(value)}
/> />
<TextCustom size="small" color="gray"> <TextCustom size="small" color="gray">
Minimal donasi Rp. 10.000 Minimal donasi Rp. 10.000

View File

@@ -5,24 +5,84 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { dummyMasterBank } from "@/lib/dummy-data/_master/bank"; import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { useAuth } from "@/hooks/use-auth";
import { apiDonationCreateInvoice } from "@/service/api-client/api-donation";
import { apiMasterBank } from "@/service/api-client/api-master";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
export default function DonationSelectBank() { export default function DonationSelectBank() {
const { id, transaction } = useLocalSearchParams(); const { user } = useAuth();
const [value, setValue] = useState<any | number>(""); const { id } = useLocalSearchParams();
const [select, setSelect] = useState<any | number>("");
const [listBank, setListBank] = useState<any>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
loadListBank();
}, []);
const loadListBank = async () => {
try {
const response = await apiMasterBank();
setListBank(response.data);
} catch (error) {
console.log("[ERROR]", error);
setListBank([]);
}
};
const handlerSubmit = async () => {
try {
setIsLoading(true);
const dataStorage = await AsyncStorage.getItem(
LOCAL_STORAGE_KEY.transactionDonation
);
if (dataStorage) {
const storage = JSON.parse(dataStorage);
const newData = {
...storage,
bankId: select,
authorId: user?.id,
};
const response = await apiDonationCreateInvoice({
id: id as string,
data: newData,
});
if (response.success) {
const invoiceId = response.data.id;
await AsyncStorage.removeItem(LOCAL_STORAGE_KEY.transactionDonation);
router.replace(
`/(application)/(user)/donation/[id]/(transaction-flow)/${invoiceId}/invoice`
);
} else {
console.log("[FAILED]", response);
}
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => isLoading={isLoading}
router.replace( disabled={!select}
`/(application)/(user)/donation/${id}/(transaction-flow)/${transaction}/invoice` onPress={() => handlerSubmit()}
)
}
> >
Pilih Pilih
</ButtonCustom> </ButtonCustom>
@@ -32,10 +92,12 @@ export default function DonationSelectBank() {
}; };
return ( return (
<ViewWrapper footerComponent={buttonSubmit()}> <ViewWrapper footerComponent={buttonSubmit()}>
<RadioGroup value={value} onChange={setValue}> <RadioGroup value={select} onChange={setSelect}>
{dummyMasterBank.map((item) => ( {_.isEmpty(listBank)
<BaseBox key={item.name}> ? []
<RadioCustom label={item.name} value={item.code} /> : listBank?.map((item: any, index: number) => (
<BaseBox key={index}>
<RadioCustom label={item.namaBank} value={item.id} />
</BaseBox> </BaseBox>
))} ))}
</RadioGroup> </RadioGroup>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
@@ -14,16 +15,43 @@ import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData"; import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising"; import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
import Donation_ProgressSection from "@/screens/Donation/ProgressSection"; import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { FontAwesome6 } from "@expo/vector-icons"; import { FontAwesome6 } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useState } from "react"; import { useCallback, useState } from "react";
export default function DonasiDetailStatus() { export default function DonasiDetailStatus() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -46,13 +74,22 @@ export default function DonasiDetailStatus() {
/> />
<ViewWrapper> <ViewWrapper>
<Donation_ComponentBoxDetailData <Donation_ComponentBoxDetailData
data={data}
bottomSection={ bottomSection={
status === "publish" && <Donation_ProgressSection id={id as string} /> status === "publish" && (
<Donation_ProgressSection id={id as string} />
)
} }
/> />
<Donation_ComponentStoryFunrising id={id as string} /> <Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
<Spacing /> <Spacing />
<Donation_ButtonStatusSection status={status as string} /> <Donation_ButtonStatusSection
id={id as string}
status={status as string}
/>
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>

View File

@@ -1,27 +1,42 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
DummyLandscapeImage, DummyLandscapeImage,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useLocalSearchParams } from "expo-router"; import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function DonationDetailStory() { export default function DonationDetailStory() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
setData(response.data.CeritaDonasi);
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom> <StackCustom>
<TextCustom> <TextCustom>{data?.pembukaan || "-"}</TextCustom>
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga <DummyLandscapeImage imageId={data?.imageId} />
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem <TextCustom>{data?.cerita || "-"}</TextCustom>
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
</TextCustom>
<DummyLandscapeImage />
<TextCustom>
Lorem {id} ipsum dolor, sit amet consectetur adipisicing elit. Fuga
quasi nam nesciunt nisi corporis alias modi, pariatur sit totam rem
fugiat ex similique magni, aliquam maiores officiis iure at adipisci.
</TextCustom>
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
); );

View File

@@ -1,7 +1,80 @@
import { ViewWrapper, StackCustom, InformationBox, TextInputCustom, Spacing, ButtonCustom } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { router } from "expo-router"; import {
ViewWrapper,
StackCustom,
InformationBox,
TextInputCustom,
Spacing,
ButtonCustom,
} from "@/components";
import {
apiDonationGetOne,
apiDonationUpdateData,
} from "@/service/api-client/api-donation";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationEditRekening() { export default function DonationEditRekening() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
namaBank: "",
rekening: "",
});
const [isLoading, setLoading] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
const resData = response.data;
console.log("[RESPONSE]", JSON.stringify(resData, null, 2));
setData({
namaBank: resData.namaBank,
rekening: resData.rekening,
});
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmitUpdate = async () => {
try {
setLoading(true);
const response = await apiDonationUpdateData({
id: id as string,
data: data,
category: "edit-bank-account",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal mengupdate data bank",
});
}
Toast.show({
type: "success",
text1: "Data bank berhasil diupdate",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -10,17 +83,22 @@ export default function DonationEditRekening() {
label="Nama Bank" label="Nama Bank"
placeholder="Masukkan nama bank" placeholder="Masukkan nama bank"
required required
value={data.namaBank}
onChangeText={(value) => setData({ ...data, namaBank: value })}
/> />
<TextInputCustom <TextInputCustom
label="Nomor Rekening" label="Nomor Rekening"
placeholder="Masukkan nomor rekening" placeholder="Masukkan nomor rekening"
required required
value={data.rekening}
onChangeText={(value) => setData({ ...data, rekening: value })}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
@@ -6,11 +7,91 @@ import {
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import API_IMAGE from "@/constants/api-storage";
import DIRECTORY_ID from "@/constants/directory-id";
import {
apiDonationGetOne,
apiDonationUpdateData,
} from "@/service/api-client/api-donation";
import { uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationEditStory() { export default function DonationEditStory() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
const [imageStory, setImageStory] = useState<string | null>(null);
const [isLoading, setLoading] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
setData(response.data.CeritaDonasi);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmitUpdate = async () => {
let newData;
try {
setLoading(true);
newData = {
...data,
};
if (imageStory) {
const responseUploadImageDonasi = await uploadFileService({
imageUri: imageStory,
dirId: DIRECTORY_ID.donasi_cerita_image,
});
newData = {
...data,
newImageId: responseUploadImageDonasi.data.id,
};
}
const response = await apiDonationUpdateData({
id: id as string,
data: newData,
category: "edit-story",
});
console.log("[RESPONSE]", JSON.stringify(response, null, 2));
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal membuat donasi",
});
}
Toast.show({
type: "success",
text1: "Donasi berhasil disimpan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -21,12 +102,23 @@ export default function DonationEditStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.pembukaan}
onChangeText={(value) => setData({ ...data, pembukaan: value })}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={
imageStory ? imageStory : API_IMAGE.GET({ fileId: data?.imageId })
}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImageStory(uri);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -39,12 +131,15 @@ export default function DonationEditStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data?.cerita}
onChangeText={(value) => setData({ ...data, cerita: value })}
/> />
<Spacing height={40} /> <Spacing height={40} />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update

View File

@@ -1,40 +1,221 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi"; import API_IMAGE from "@/constants/api-storage";
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori"; import DIRECTORY_ID from "@/constants/directory-id";
import { router } from "expo-router"; import {
apiDonationGetOne,
apiDonationUpdateData,
} from "@/service/api-client/api-donation";
import { apiMasterDonation } from "@/service/api-client/api-master";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
interface IEditDonation {
donasiMaster_KategoriId: string;
donasiMaster_DurasiId: string;
title: string;
target: string;
imageId: string;
}
export default function DonationEdit() { export default function DonationEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<IEditDonation>({
donasiMaster_DurasiId: "",
donasiMaster_KategoriId: "",
title: "",
target: "",
imageId: "",
});
const [image, setImage] = useState<string | null>(null);
const [listCategory, setListCategory] = useState<any[]>([]);
const [listDuration, setListDuration] = useState<any[]>([]);
const [loadList, setLoadList] = useState<boolean>(false);
const [isLoading, setLoading] = useState(false);
const displayTarget = formatCurrencyDisplay(data?.target);
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
const numeric = text.replace(/\D/g, "");
setData((prev: any) => ({ ...prev, [field]: numeric }));
};
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadList();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
if (response.success) {
setData({
donasiMaster_DurasiId: response.data.donasiMaster_DurasiId,
donasiMaster_KategoriId: response.data.donasiMaster_KategoriId,
title: response.data.title,
target: response.data.target,
imageId: response.data.imageId,
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiMasterDonation({ category: "" });
setListCategory(response.data.category);
setListDuration(response.data.duration);
} catch (error) {
console.log(["ERROR"], error);
setListCategory([]);
setListDuration([]);
} finally {
setLoadList(false);
}
};
const validateData = async () => {
if (
!data.donasiMaster_DurasiId ||
!data.donasiMaster_KategoriId ||
!data.title ||
!data.target ||
!data.imageId
) {
Toast.show({
type: "error",
text1: "Harap lengkapi data",
});
return false;
}
return true;
};
const handlerSubmitUpdate = async () => {
const isValid = await validateData();
if (!isValid) {
return;
}
try {
let newData;
newData = {
...data,
};
setLoading(true);
if (image && image) {
const uploadNewImage = await uploadFileService({
dirId: DIRECTORY_ID.donasi_image,
imageUri: image,
});
if (!uploadFileService) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
newData = {
...data,
newImageId: uploadNewImage.data.id,
};
}
const response = await apiDonationUpdateData({
id: id as string,
data: newData,
category: "edit-donation",
});
if (!response.success) {
Toast.show({
type: "error",
text1: response.message,
});
return;
}
Toast.show({
type: "success",
text1: "Donasi berhasil diperbarui",
});
router.back();
} catch (error) {
console.log("[ERROR UPDATE DONASI]", error);
} finally {
setLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}>
<InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." /> <InformationBox text="Lengkapi semua data di bawah untuk selanjutnya mengisi cerita penggalangan dana." />
{!data || loadList ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom <TextInputCustom
label="Judul Donasi" label="Judul Donasi"
placeholder="Masukkan Judul Donasi" placeholder="Masukkan Judul Donasi"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextInputCustom <TextInputCustom
iconLeft="Rp."
label="Target Donasi" label="Target Donasi"
placeholder="Masukkan Target Donasi" placeholder="Masukkan Target Donasi"
required required
keyboardType="numeric" keyboardType="numeric"
value={displayTarget}
onChangeText={handleChangeCurrency("target")}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={image ? image : API_IMAGE.GET({ fileId: data?.imageId })}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}} }}
icon="upload" icon="upload"
> >
@@ -43,36 +224,52 @@ export default function DonationEdit() {
<Spacing /> <Spacing />
<SelectCustom <SelectCustom
data={dummyDonasiKategori.map((item) => ({ data={
label: item.label, _.isEmpty(listCategory)
value: item.value, ? []
}))} : listCategory?.map((item) => ({
onChange={(value) => console.log(value)} label: item.name,
value: item.id,
}))
}
label="Pilih Kategori Donasi" label="Pilih Kategori Donasi"
placeholder="Pilih Kategori Donasi" placeholder="Pilih Kategori Donasi"
required required
value={data?.donasiMaster_KategoriId}
onChange={(value: any) =>
setData({ ...data, donasiMaster_KategoriId: value })
}
/> />
<SelectCustom <SelectCustom
data={dummyDonasiDurasi.map((item) => ({ data={
label: item.label, _.isEmpty(listDuration)
value: item.value, ? []
}))} : listDuration?.map((item) => ({
onChange={(value) => console.log(value)} label: item.name + " hari",
value: item.id,
}))
}
label="Pilih Durasi Donasi" label="Pilih Durasi Donasi"
placeholder="Pilih Durasi Donasi" placeholder="Pilih Durasi Donasi"
required required
value={data?.donasiMaster_DurasiId}
onChange={(value: any) =>
setData({ ...data, donasiMaster_DurasiId: value })
}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.back(); handlerSubmitUpdate();
}} }}
> >
Update Update
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>
)}
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
); );

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -9,24 +10,54 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconNews } from "@/components/_Icon"; import { IconNews } from "@/components/_Icon";
import { useAuth } from "@/hooks/use-auth";
import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData"; import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData";
import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising"; import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising";
import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising"; import Donation_ComponentStoryFunrising from "@/screens/Donation/ComponentStoryFunrising";
import Donation_ProgressSection from "@/screens/Donation/ProgressSection"; import Donation_ProgressSection from "@/screens/Donation/ProgressSection";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiDonationGetOne } from "@/service/api-client/api-donation";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function DonasiDetailBeranda() { export default function DonasiDetailBeranda() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("ID ", id);
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "permanent",
});
console.log("[RES GET ONE]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const buttonSection = ( const buttonSection = (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => onPress={() => router.navigate(`/donation/${id}/(transaction-flow)`)}
router.navigate(`/donation/${id}/(transaction-flow)`)
}
> >
Donasi Donasi
</ButtonCustom> </ButtonCustom>
@@ -40,16 +71,23 @@ export default function DonasiDetailBeranda() {
options={{ options={{
title: `Detail Donasi`, title: `Detail Donasi`,
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, headerRight: () =>
user?.id === data?.Author?.id ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
}} }}
/> />
<ViewWrapper footerComponent={buttonSection}> <ViewWrapper footerComponent={buttonSection}>
<StackCustom> <StackCustom>
<Donation_ComponentBoxDetailData <Donation_ComponentBoxDetailData
data={data}
bottomSection={<Donation_ProgressSection id={id as string} />} bottomSection={<Donation_ProgressSection id={id as string} />}
/> />
<Donation_ComponentInfoFundrising id={id as string} /> <Donation_ComponentInfoFundrising dataAuthor={data?.Author} />
<Donation_ComponentStoryFunrising id={id as string} /> <Donation_ComponentStoryFunrising
id={id as string}
dataStory={data?.CeritaDonasi}
/>
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>

View File

@@ -1,32 +1,68 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarCustom, AvatarComp,
BaseBox, BaseBox,
ButtonCustom, ButtonCustom,
CenterCustom,
Grid, Grid,
LoaderCustom,
Spacing, Spacing,
TextCustom, TextCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; import Donation_BoxPublish from "@/screens/Donation/BoxPublish";
import React from "react"; import { apiDonationFundrising } from "@/service/api-client/api-donation";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
import { View } from "react-native";
export default function DonationInformationFunrising() { export default function DonationInformationFunrising() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadList(true);
const response = await apiDonationFundrising({ id: id as string });
setData(response?.data?.user);
setList(response?.data?.donasi);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
<BaseBox> <BaseBox>
<Grid> <Grid>
<Grid.Col span={6} style={{ justifyContent: "center" }}> <Grid.Col span={6} style={{ justifyContent: "center" }}>
<CenterCustom> <View
<AvatarCustom size="lg" /> style={{
flexDirection: "column",
alignItems: "center",
gap: 10,
}}
>
<AvatarComp size="lg" fileId={data?.Profile?.imageId} />
<TextCustom bold size="large" truncate> <TextCustom bold size="large" truncate>
@Username @{data?.username}
</TextCustom> </TextCustom>
</CenterCustom> </View>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ justifyContent: "center" }}> <Grid.Col span={6} style={{ justifyContent: "center" }}>
<ButtonCustom href={`/profile/1234`}> <ButtonCustom href={`/profile/${data?.Profile?.id}`}>
Kunjungi Profile Kunjungi Profile
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
@@ -35,9 +71,15 @@ export default function DonationInformationFunrising() {
<Spacing /> <Spacing />
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<Donation_BoxPublish key={index} id={index.toString()} /> <LoaderCustom />
))} ) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray" size="small">Belum ada data</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Donation_BoxPublish key={index} id={item?.id} data={item} />
))
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
@@ -9,9 +10,107 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import {
apiDonationCreate,
apiDonationGetOne,
} from "@/service/api-client/api-donation";
import { uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationCreateStory() { export default function DonationCreateStory() {
const { user } = useAuth();
const { id } = useLocalSearchParams();
const [temporary, setTemporary] = useState<any>();
const [data, setData] = useState({
pembukaan: "",
cerita: "",
namaBank: "",
rekening: "",
});
const [imageStory, setImageStory] = useState<string | null>(null);
const [isLoading, setLoading] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiDonationGetOne({
id: id as string,
category: "temporary",
});
setTemporary(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmit = async () => {
if (_.values(data).includes("")) {
Toast.show({
type: "error",
text1: "Harap isi semua data",
});
return;
}
try {
setLoading(true);
const responseUploadImageDonasi = await uploadFileService({
imageUri: imageStory,
dirId: DIRECTORY_ID.donasi_cerita_image,
});
const newData = {
// Data Donasi
temporaryId: temporary?.id,
authorId: user?.id,
title: temporary?.title,
target: temporary?.target,
donasiMaster_KategoriId: temporary?.donasiMaster_KategoriId,
donasiMaster_DurasiId: temporary?.donasiMaster_DurasiId,
imageId: temporary?.imageId,
// Data Bank
namaBank: data.namaBank,
rekening: data.rekening,
// Data Cerita
imageCeritaId: responseUploadImageDonasi.data.id,
pembukaan: data.pembukaan,
cerita: data.cerita,
};
const response = await apiDonationCreate({
data: newData,
category: "permanent",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal membuat donasi",
});
}
Toast.show({
type: "success",
text1: "Donasi berhasil disimpan",
});
router.replace("/donation/status");
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -22,6 +121,8 @@ export default function DonationCreateStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.pembukaan}
onChangeText={(value) => setData({ ...data, pembukaan: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Tujuan Donasi" label="Tujuan Donasi"
@@ -29,12 +130,19 @@ export default function DonationCreateStory() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.cerita}
onChangeText={(value) => setData({ ...data, cerita: value })}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={imageStory || ""} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImageStory(uri);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -47,17 +155,23 @@ export default function DonationCreateStory() {
label="Nama Bank" label="Nama Bank"
placeholder="Masukkan nama bank" placeholder="Masukkan nama bank"
required required
value={data.namaBank}
onChangeText={(value) => setData({ ...data, namaBank: value })}
/> />
<TextInputCustom <TextInputCustom
label="Nomor Rekening" label="Nomor Rekening"
placeholder="Masukkan nomor rekening" placeholder="Masukkan nomor rekening"
required required
keyboardType="numeric"
value={data.rekening}
onChangeText={(value) => setData({ ...data, rekening: value })}
/> />
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.replace(`/donation/(tabs)/status`); handlerSubmit();
}} }}
> >
Simpan Simpan

View File

@@ -3,17 +3,127 @@ import {
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyDonasiDurasi } from "@/lib/dummy-data/donasi/durasi"; import DIRECTORY_ID from "@/constants/directory-id";
import { dummyDonasiKategori } from "@/lib/dummy-data/donasi/kategori"; import { apiDonationCreate } from "@/service/api-client/api-donation";
import { router } from "expo-router"; import { apiMasterDonation } from "@/service/api-client/api-master";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function DonationCreate() { export default function DonationCreate() {
const [listCategory, setListCategory] = useState<any[]>([]);
const [listDuration, setListDuration] = useState<any[]>([]);
const [loadList, setLoadList] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [image, setImage] = useState<string | null>(null);
const [data, setData] = useState({
kategoriId: "",
title: "",
target: "",
durasiId: "",
});
const displayTarget = formatCurrencyDisplay(data.target);
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
const numeric = text.replace(/\D/g, "");
setData((prev) => ({ ...prev, [field]: numeric }));
};
useFocusEffect(
useCallback(() => {
onLoadList();
}, [])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiMasterDonation({ category: "" });
setListCategory(response.data.category);
setListDuration(response.data.duration);
} catch (error) {
console.log(["ERROR"], error);
setListCategory([]);
setListDuration([]);
} finally {
setLoadList(false);
}
};
const validateData = () => {
if (!data.title || !data.target || !data.durasiId || !data.kategoriId) {
Toast.show({
type: "error",
text1: "Harap isi semua data",
});
return false;
}
return true;
};
const handlerSubmit = async () => {
if (!validateData()) {
return;
}
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
imageUri: image,
dirId: DIRECTORY_ID.donasi_image,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUploadImage.data.id;
const newData = {
title: data.title,
target: data.target,
durasiId: data.durasiId,
kategoriId: data.kategoriId,
imageId: imageId,
};
const response = await apiDonationCreate({
data: newData,
category: "temporary",
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal membuat donasi",
});
return;
}
router.push(`/donation/create-story?id=${response.data.id}`);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
@@ -23,18 +133,28 @@ export default function DonationCreate() {
label="Judul Donasi" label="Judul Donasi"
placeholder="Masukkan Judul Donasi" placeholder="Masukkan Judul Donasi"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextInputCustom <TextInputCustom
iconLeft="Rp."
label="Target Donasi" label="Target Donasi"
placeholder="Masukkan Target Donasi" placeholder="Masukkan Target Donasi"
required required
keyboardType="numeric" keyboardType="numeric"
value={displayTarget}
onChangeText={handleChangeCurrency("target")}
/> />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image || ""} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri: ({ uri }) => {
setImage(uri);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -42,31 +162,52 @@ export default function DonationCreate() {
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing />
{loadList ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
data={dummyDonasiKategori.map((item) => ({ data={
label: item.label, _.isEmpty(listCategory)
value: item.value, ? []
}))} : listCategory?.map((item: any) => ({
onChange={(value) => console.log(value)} label: item.name,
value: item.id,
}))
}
label="Pilih Kategori Donasi" label="Pilih Kategori Donasi"
placeholder="Pilih Kategori Donasi" placeholder="Pilih Kategori Donasi"
required required
value={data.kategoriId}
onChange={(value: any) => setData({ ...data, kategoriId: value })}
/> />
)}
{loadList ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
data={dummyDonasiDurasi.map((item) => ({ data={
label: item.label, _.isEmpty(listDuration)
value: item.value, ? []
}))} : listDuration?.map((item: any) => ({
onChange={(value) => console.log(value)} label: item.name + `${" hari"}`,
value: item.id,
}))
}
label="Pilih Durasi Donasi" label="Pilih Durasi Donasi"
placeholder="Pilih Durasi Donasi" placeholder="Pilih Durasi Donasi"
required required
value={data.durasiId}
onChange={(value: any) => setData({ ...data, durasiId: value })}
/> />
)}
<Spacing /> <Spacing />
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
router.replace("/donation/create-story"); handlerSubmit();
// router.push(`/donation/create-story?id=${"dasdsadsa"}`);
}} }}
> >
Selanjutnya Selanjutnya

View File

@@ -1,43 +1,115 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarCustom,
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, LoaderCustom,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper ViewWrapper
} from "@/components"; } from "@/components";
import React from "react"; import { useAuth } from "@/hooks/use-auth";
import {
apiEventGetAll
} from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import React, { useCallback, useState } from "react";
export default function EventContribution() { export default function EventContribution() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id])
);
async function onLoadData() {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: "contribution",
userId: user?.id,
});
console.log("[DATA] ", JSON.stringify(response.data, null, 2));
if (response.success) {
setListData(response.data);
// const responseListParticipants = await apiEventListOfParticipants({
// id: response?.data?.Event?.id,
// });
// console.log(
// "[LIST PARTICIPANTS]",
// JSON.stringify(responseListParticipants.data, null, 2)
// );
// if (responseListParticipants.success) {
// setListParticipants(responseListParticipants.data);
// }
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadList ? (
<BoxWithHeaderSection key={index} href={`/event/${index}/contribution`}> <LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada kontribusi</TextCustom>
) : (
listData.map((item: any, index: number) => (
<BoxWithHeaderSection
key={index}
href={`/event/${item?.Event?.id}/contribution`}
>
<StackCustom> <StackCustom>
<AvatarUsernameAndOtherComponent <AvatarUsernameAndOtherComponent
avatarHref={`/profile/${index}`} avatar={item?.Event?.Author?.Profile?.imageId}
avatarHref={`/profile/${item?.Event?.Author?.Profile?.id}`}
name={item?.Event?.Author?.username}
rightComponent={ rightComponent={
<TextCustom truncate> <TextCustom truncate>
{new Date().toLocaleDateString()} {dateTimeView({
date: item?.Event?.tanggal,
withoutTime: true,
})}
</TextCustom> </TextCustom>
} }
/> />
<TextCustom bold align="center" size="xlarge"> <TextCustom bold align="center" size="xlarge" truncate={2}>
Judul Event Disini {item?.Event?.title}
</TextCustom> </TextCustom>
<Spacing height={0} />
<Grid> {/* <Grid>
{Array.from({ length: 4 }).map((_, index2) => ( {item?.Event?.Event_Peserta?.map(
<Grid.Col span={3} key={index2}> (item2: any, index2: number) => (
<AvatarCustom size="sm" href={`/profile/${index2}`} /> <Grid.Col
style={{ alignItems: "center" }}
span={12 / item?.Event?.Event_Peserta?.length}
key={index2}
>
<AvatarComp
size="base"
href={`/profile/${item2?.User?.Profile?.id}`}
fileId={item2?.User?.Profile?.imageId}
/>
</Grid.Col> </Grid.Col>
))} )
</Grid> )}
</Grid> */}
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,12 +1,41 @@
import { ButtonCustom, Spacing, TextCustom } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection"; import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { useState } from "react"; import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import _ from "lodash";
import { useEffect, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function EventHistory() { export default function EventHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all"); const [activeCategory, setActiveCategory] = useState<string | null>("all");
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useEffect(() => {
onLoadData({ userId: user?.id });
}, [user?.id, activeCategory]);
async function onLoadData({ userId }: { userId?: string }) {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
userId: userId,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item); setActiveCategory(item);
@@ -52,17 +81,24 @@ export default function EventHistory() {
return ( return (
<ViewWrapper headerComponent={headerComponent} hideFooter> <ViewWrapper headerComponent={headerComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada riwayat</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Event_BoxPublishSection <Event_BoxPublishSection
key={index.toString()} key={index.toString()}
id={index.toString()} data={item}
username={`Riwayat ${activeCategory === "main" ? "Saya" : "Semua"}`}
rightComponentAvatar={ rightComponentAvatar={
<TextCustom>{new Date().toLocaleDateString()}</TextCustom> <TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
} }
href={`/event/${index}/history`} href={`/event/${item.id}/history`}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,9 +1,36 @@
import { LoaderCustom, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import FloatingButton from "@/components/Button/FloatingButton"; import FloatingButton from "@/components/Button/FloatingButton";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection"; import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { router } from "expo-router"; import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function EventBeranda() { export default function EventBeranda() {
const [listData, setListData] = useState([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiEventGetAll({category: "beranda"});
// console.log("Response", JSON.stringify(response.data, null, 2));
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
@@ -11,13 +38,24 @@ export default function EventBeranda() {
<FloatingButton onPress={() => router.push("/event/create")} /> <FloatingButton onPress={() => router.push("/event/create")} />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada event</TextCustom>
) : (
listData.map((item: any, index) => (
<Event_BoxPublishSection <Event_BoxPublishSection
key={index} key={index}
id={index.toString()} href={`/event/${item.id}/publish`}
href={`/event/${index}/publish`} data={item}
rightComponentAvatar={
<TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,27 +1,57 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BoxWithHeaderSection, BoxWithHeaderSection,
Grid, Grid,
LoaderCustom,
ScrollableCustom, ScrollableCustom,
StackCustom, StackCustom,
TextCustom TextCustom,
} from "@/components"; } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { useState } from "react"; import { apiEventGetByStatus } from "@/service/api-client/api-event";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function EventStatus() { export default function EventStatus() {
const id = "test-id-event"; const { user } = useAuth();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
const [listData, setListData] = useState([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory, id])
);
async function onLoadData() {
try {
setLoadingGetData(true);
const response = await apiEventGetByStatus({
id: id!,
status: activeCategory!,
});
// console.log("Response", JSON.stringify(response.data, null, 2));
setListData(response.data);
} catch (error) {
console.log(error);
} finally {
setLoadingGetData(false);
}
}
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
}; };
const scrollComponent = ( const tabsComponent = (
<ScrollableCustom <ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({ data={dummyMasterStatus.map((e, i) => ({
id: i, id: i,
@@ -34,30 +64,36 @@ export default function EventStatus() {
); );
return ( return (
<ViewWrapper headerComponent={scrollComponent}> <ViewWrapper headerComponent={tabsComponent}>
<BoxWithHeaderSection href={`/event/${id}/${activeCategory}/detail-event`}> {loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, i) => (
<BoxWithHeaderSection
key={i}
href={`/event/${item.id }/${activeCategory}/detail-event`}
>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<Grid> <Grid>
<Grid.Col span={8}> <Grid.Col span={8}>
<TextCustom truncate bold> <TextCustom truncate bold>
Lorem ipsum,{" "} {item?.title}
<TextCustom color="green">{activeCategory}</TextCustom> dolor
sit amet consectetur adipisicing elit.
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}> <Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>{new Date().toLocaleDateString()}</TextCustom> <TextCustom>
{new Date(item?.tanggal).toLocaleDateString()}
</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<TextCustom truncate={2}> <TextCustom truncate={2}>{item?.deskripsi}</TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur
eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora,
atque. Aperiam minima asperiores dicta perferendis quis adipisci,
dolore optio porro!
</TextCustom>
</StackCustom> </StackCustom>
</BoxWithHeaderSection> </BoxWithHeaderSection>
))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
DotButton, DotButton,
@@ -11,17 +12,64 @@ import {
} from "@/components"; } from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import Event_AlertButtonStatusSection from "@/screens/Event/AlertButtonStatusSection";
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection"; import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft"; import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiEventGetOne } from "@/service/api-client/api-event";
import { useState } from "react"; import { dateTimeView } from "@/utils/dateTimeView";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function EventDetailStatus() { export default function EventDetailStatus() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openAlert, setOpenAlert] = useState(false); // const [openAlert, setOpenAlert] = useState(false);
const [openDeleteAlert, setOpenDeleteAlert] = useState(false);
const [data, setData] = useState<any>();
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
async function onLoadData() {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
}
const listData = [
{
title: "Lokasi",
value: data?.lokasi || "-",
},
{
title: "Tipe Acara",
value: data?.EventMaster_TipeAcara?.name || "-",
},
{
title: "Tanggal Mulai",
value: dateTimeView({ date: data?.tanggal }) || "-",
},
{
title: "Tanggal Berakhir",
value: dateTimeView({ date: data?.tanggalSelesai }) || "-",
},
{
title: "Deskripsi",
value: data?.deskripsi || "-",
},
];
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -45,7 +93,7 @@ export default function EventDetailStatus() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom bold align="center" size="xlarge"> <TextCustom bold align="center" size="xlarge">
Judul event {status} {data?.title || "-"}
</TextCustom> </TextCustom>
{listData.map((item, index) => ( {listData.map((item, index) => (
<Grid key={index}> <Grid key={index}>
@@ -60,9 +108,8 @@ export default function EventDetailStatus() {
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<Event_ButtonStatusSection <Event_ButtonStatusSection
id={id as string}
status={status as string} status={status as string}
onOpenAlert={setOpenAlert}
onOpenDeleteAlert={setOpenDeleteAlert}
/> />
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
@@ -70,7 +117,7 @@ export default function EventDetailStatus() {
<DrawerCustom <DrawerCustom
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerDraftEvent({ id: id as string }) as any} data={menuDrawerDraftEvent({ id: id as string }) as any}
@@ -78,40 +125,6 @@ export default function EventDetailStatus() {
onPressItem={handlePress as any} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
<Event_AlertButtonStatusSection
id={id as string}
status={status as string}
openAlert={openAlert}
setOpenAlert={setOpenAlert}
openDeleteAlert={openDeleteAlert}
setOpenDeleteAlert={setOpenDeleteAlert}
/>
</> </>
); );
} }
const listData = [
{
title: "Lokasi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
},
{
title: "Tipe Acara",
value: "Workshop",
},
{
title: "Tanggal Mulai",
value: "Senin, 18 Juli 2025, 10:00 WIB",
},
{
title: "Tanggal Berakhir",
value: "Selasa, 19 Juli 2025, 12:00 WIB",
},
{
title: "Deskripsi",
value:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur eveniet ab eum ducimus tempore a quia deserunt quisquam. Tempora, atque. Aperiam minima asperiores dicta perferendis quis adipisci, dolore optio porro!",
},
];

View File

@@ -1,20 +1,43 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
ViewWrapper,
Spacing, Spacing,
ViewWrapper,
} from "@/components"; } from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection"; import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish"; import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { apiEventGetOne } from "@/service/api-client/api-event";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useEffect, useState } from "react";
export default function EventDetailContribution() { export default function EventDetailContribution() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any>();
const [isLoadData, setIsLoadData] = useState(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
@@ -32,18 +55,22 @@ export default function EventDetailContribution() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Event_BoxDetailPublishSection /> {isLoadData ? (
<LoaderCustom />
) : (
<Event_BoxDetailPublishSection data={data} />
)}
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })} data={menuDrawerPublishEvent({ id: id as string })}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
</> </>

View File

@@ -1,106 +1,271 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event"; import {
apiEventGetOne,
apiEventUpdateData,
} from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import React, { useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native"; import Toast from "react-native-toast-message";
export default function EventEdit() { export default function EventEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>();
// {
// title: "",
// lokasi: "",
// deskripsi: "",
// eventMaster_TipeAcaraId: "",
// tanggal: "",
// tanggalSelesai: "",
// authorId: "",
// }
const [listTypeEvent, setListTypeEvent] = useState([]);
const [selectedDate, setSelectedDate] = useState< const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >();
const [selectedEndDate, setSelectedEndDate] = useState< const [selectedEndDate, setSelectedEndDate] = useState<
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >();
const handlerSubmit = () => { const [isLoading, setIsLoading] = useState(false);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
async function onLoadData() {
try { try {
if (selectedDate) { setIsLoadData(true);
console.log("Tanggal yang dipilih:", selectedDate); const response = await apiEventGetOne({ id: id as string });
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString()); if (response.success) {
// Kirim ke API atau proses lanjutan setData(response.data);
} else { setSelectedDate(new Date(response.data.tanggal));
console.log("Tanggal belum dipilih"); setSelectedEndDate(new Date(response.data.tanggalSelesai));
} }
if (selectedEndDate) {
console.log("Tanggal yang dipilih:", selectedEndDate);
console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
// Kirim ke API atau proses lanjutan
} else {
console.log("Tanggal berakhir belum dipilih");
}
console.log("Data berhasil terupdate");
router.back()
} catch (error) { } catch (error) {
console.log(error); console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
}
useEffect(() => {
onLoadMasterEventType();
}, []);
const onLoadMasterEventType = async () => {
try {
const response = await apiMasterEventType();
setListTypeEvent(response.data);
} catch (error) {
console.log("Error onLoadMasterEventType", error);
} }
}; };
const buttonSubmit = ( const validateDate = async () => {
<ButtonCustom title="Update" onPress={handlerSubmit} /> if (
); data?.title === "" ||
data?.lokasi === "" ||
data?.deskripsi === "" ||
data?.eventMaster_TipeAcaraId === ""
) {
Toast.show({
type: "info",
text1: "Info",
text2: "Lengkapi semua data",
});
return false;
}
const startDate = new Date(selectedDate as any);
const endDate = new Date(selectedEndDate as any);
if (startDate >= endDate) {
Toast.show({
type: "info",
text1: "Info",
text2: "Ubah tanggal berakhirnya event",
});
return false;
}
return true;
};
const handlerSubmit = async () => {
const isValid = await validateDate();
if (!isValid) return;
try {
setIsLoading(true);
const newData = {
...data,
tanggal: new Date(selectedDate as any).toISOString(),
tanggalSelesai: new Date(selectedEndDate as any).toISOString(),
};
const response = await apiEventUpdateData({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
return router.back();
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
const validateDateRange = (
selectedDate: string | Date,
selectedEndDate: string | Date
): { isValid: boolean; error?: string } => {
const startDate = new Date(selectedDate);
const endDate = new Date(selectedEndDate);
// Cek apakah tanggal valid
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
return {
isValid: false,
error: "Invalid date provided",
};
}
if (startDate >= endDate) {
return {
isValid: false,
error: "Ubah tanggal berakhirnya event",
};
}
return {
isValid: true,
error: undefined,
};
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{isLoadData ? (
<LoaderCustom />
) : (
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextInputCustom <TextInputCustom
placeholder="Masukkan nama event" placeholder="Masukkan nama event"
label="Nama Event" label="Nama Event"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<SelectCustom <SelectCustom
label="Tipe Event" label="Tipe Event"
placeholder="Pilih tipe event" placeholder="Pilih tipe event"
data={masterTypeEvent} data={listTypeEvent.map((item: any) => ({
onChange={(value) => console.log(value)} label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value) => {
console.log(value);
setData({ ...data, eventMaster_TipeAcaraId: value });
}}
/> />
<TextInputCustom <TextInputCustom
label="Lokasi" label="Lokasi"
placeholder="Masukkan lokasi event" placeholder="Masukkan lokasi event"
required required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/> />
<DateTimePickerCustom <DateTimePickerCustom
minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai" label="Tanggal & Waktu Mulai"
required required
onChange={(date: Date) => { value={selectedDate as any}
onChange={(date: any) => {
setSelectedDate(date as any); setSelectedDate(date as any);
}} }}
value={selectedDate as any}
minimumDate={new Date(Date.now())}
/> />
<StackCustom gap={0}>
<DateTimePickerCustom <DateTimePickerCustom
minimumDate={selectedDate as any}
label="Tanggal & Waktu Berakhir" label="Tanggal & Waktu Berakhir"
required required
onChange={(date: Date) => { value={selectedEndDate as any}
onChange={(date: any) => {
setSelectedEndDate(date as any); setSelectedEndDate(date as any);
}} }}
value={selectedEndDate as any}
/> />
{/* Muncul */}
{validateDateRange(selectedDate as any, selectedEndDate as any)
.isValid ? (
<TextCustom style={{ color: "green" }}>
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
).error
}
</TextCustom>
) : (
<TextCustom style={{ color: "red" }}>
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
).error
}
</TextCustom>
)}
<Spacing />
</StackCustom>
<TextAreaCustom <TextAreaCustom
label="Deskripsi" label="Deskripsi"
placeholder="Masukkan deskripsi event" placeholder="Masukkan deskripsi event"
required required
showCount showCount
maxLength={100} maxLength={100}
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
{buttonSubmit} <ButtonCustom
isLoading={isLoading}
title="Update"
onPress={handlerSubmit}
/>
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
DotButton, DotButton,
DrawerCustom, DrawerCustom,
@@ -9,12 +10,29 @@ import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection"; import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish"; import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { apiEventGetOne } from "@/service/api-client/api-event";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useEffect, useState } from "react";
export default function EventDetailHistory() { export default function EventDetailHistory() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState();
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
@@ -32,7 +50,7 @@ export default function EventDetailHistory() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Event_BoxDetailPublishSection /> <Event_BoxDetailPublishSection data={data} />
<Spacing /> <Spacing />
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
@@ -43,7 +61,7 @@ export default function EventDetailHistory() {
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })} data={menuDrawerPublishEvent({ id: id as string })}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
</> </>

View File

@@ -1,17 +1,102 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox, BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import {
apiEventGetOne,
apiEventListOfParticipants,
} from "@/service/api-client/api-event";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { View } from "react-native";
export default function EventListOfParticipants() { export default function EventListOfParticipants() {
const { id } = useLocalSearchParams();
const [startDate, setStartDate] = useState();
const [listData, setListData] = useState([]);
const [isLoadData, setIsLoadData] = useState(false);
useEffect(() => {
handlerLoadData();
}, [id]);
const handlerLoadData = () => {
try {
setIsLoadData(true);
onLoadData();
onLoadList();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const onLoadData = async () => {
try {
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setStartDate(response.data.tanggal);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const onLoadList = async () => {
try {
const response = await apiEventListOfParticipants({ id: id as string });
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {isLoadData ? (
<BaseBox key={index} paddingBlock={0}> <LoaderCustom />
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${index}`} /> ) : listData.length === 0 ? (
<TextCustom align="center">Belum ada peserta</TextCustom>
) : (
listData.map((item: any, index: number) => (
<BaseBox key={index}>
<AvatarUsernameAndOtherComponent
avatar={item?.User?.Profile?.imageId}
name={item?.User?.username}
avatarHref={`/profile/${item?.User?.Profile?.id}`}
rightComponent={
new Date().getTime() > new Date(startDate as any).getTime() ? (
<View
style={{
justifyContent: "flex-end",
}}
>
<BadgeCustom color={item?.isPresent ? "green" : "red"}>
{item?.isPresent ? "Hadir" : "Tidak Hadir"}
</BadgeCustom>
</View>
) : (
<View
style={{
justifyContent: "flex-end",
}}
>
<BadgeCustom color="gray">-</BadgeCustom>
</View>
)
}
/>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,22 +1,73 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
ButtonCustom, ButtonCustom,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, ViewWrapper,
ViewWrapper
} from "@/components"; } from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection"; import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish"; import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiEventCheckParticipants,
import { Alert } from "react-native"; apiEventGetOne,
apiEventJoin,
} from "@/service/api-client/api-event";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function EventDetailPublish() { export default function EventDetailPublish() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [isLoadingData, setIsLoadingData] = useState(false);
const [isLoadingJoin, setIsLoadingJoin] = useState(false);
const [data, setData] = useState();
const [isParticipant, setIsParticipant] = useState<boolean | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
async function onLoadData() {
try {
setIsLoadingData(true);
const response = await apiEventGetOne({ id: id as string });
if (response.success) {
setData(response.data);
const responseCheckParticipants = await apiEventCheckParticipants({
id: id as string,
userId: user?.id as string,
});
if (
responseCheckParticipants.success &&
responseCheckParticipants.data
) {
setIsParticipant(true);
}
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingData(false);
}
}
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path); console.log("PATH ", item.path);
@@ -24,15 +75,61 @@ export default function EventDetailPublish() {
setOpenDrawer(false); setOpenDrawer(false);
}; };
const footerButton = ( const handlerJoin = async () => {
const userId = user?.id;
if (!userId) {
return Toast.show({
type: "error",
text2: "Anda belum login",
});
}
try {
setIsLoadingJoin(true);
const response = await apiEventJoin({
id: id as string,
userId: userId as string,
});
if (response.success) {
router.navigate(
`/(application)/(user)/event/${id}/list-of-participants`
);
Toast.show({
type: "success",
text1: "Anda berhasil join",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingJoin(false);
}
};
const footerButton = () => {
return (
<>
<ButtonCustom <ButtonCustom
disabled={isParticipant as any}
isLoading={isLoadingJoin}
backgroundColor="green" backgroundColor="green"
textColor="white" textColor="white"
onPress={() => Alert.alert("Anda berhasil join event ini")} onPress={() =>
AlertDefaultSystem({
title: "Join event",
message: "Anda yakin ingin join sebagai peserta event ?",
textLeft: "Tidak",
textRight: "Ya",
onPressLeft: () => {},
onPressRight: () => handlerJoin(),
})
}
> >
Join {isParticipant ? "Anda sudah tergabung" : "Join"}
</ButtonCustom> </ButtonCustom>
</>
); );
};
return ( return (
<> <>
@@ -44,19 +141,25 @@ export default function EventDetailPublish() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Event_BoxDetailPublishSection footerButton={footerButton} /> {isLoadingData ? (
<Spacing /> <LoaderCustom />
) : (
<Event_BoxDetailPublishSection
data={data}
footerButton={footerButton()}
/>
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
height={250} height={"auto"}
> >
<MenuDrawerDynamicGrid <MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })} data={menuDrawerPublishEvent({ id: id as string })}
columns={4} columns={4}
onPressItem={handlePress} onPressItem={handlePress as any}
/> />
</DrawerCustom> </DrawerCustom>
</> </>

View File

@@ -1,19 +1,51 @@
import { import {
ButtonCustom, ButtonCustom,
SelectCustom, SelectCustom,
Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event"; import { useAuth } from "@/hooks/use-auth";
import { apiEventCreate } from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { Platform } from "react-native"; import Toast from "react-native-toast-message";
interface EventCreateProps {
title?: string;
lokasi?: string;
deskripsi?: string;
eventMaster_TipeAcaraId?: string;
tanggal?: string;
tanggalSelesai?: string;
authorId?: string;
}
export default function EventCreate() { export default function EventCreate() {
const [data, setData] = useState<EventCreateProps>();
const [listTypeEvent, setListTypeEvent] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const { user } = useAuth();
useEffect(() => {
onLoadMasterEventType();
}, []);
const onLoadMasterEventType = async () => {
try {
const response = await apiMasterEventType();
setListTypeEvent(response.data);
} catch (error) {
console.log("Error onLoadMasterEventType", error);
}
};
const [selectedDate, setSelectedDate] = useState< const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >(null);
@@ -22,35 +54,76 @@ export default function EventCreate() {
Date | DateTimePickerEvent | null Date | DateTimePickerEvent | null
>(null); >(null);
const handlerSubmit = () => { const handlerSubmit = async () => {
if (
!data?.title ||
!data?.lokasi ||
!data?.deskripsi ||
!data?.eventMaster_TipeAcaraId
) {
Toast.show({
type: "info",
text1: "Info",
text2: "Lengkapi semua data",
});
return;
}
if (!selectedDate || !selectedEndDate) {
Toast.show({
type: "info",
text1: "Info",
text2: "Pilih tanggal mulai dan berakhir",
});
return;
}
// if (selectedDate) {
// console.log("Tanggal yang dipilih:", selectedDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal belum dipilih");
// }
// if (selectedEndDate) {
// console.log("Tanggal yang dipilih:", selectedEndDate);
// console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
// // Kirim ke API atau proses lanjutan
// } else {
// console.log("Tanggal berakhir belum dipilih");
// }
try { try {
if (selectedDate) { setIsLoading(true);
console.log("Tanggal yang dipilih:", selectedDate);
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// Kirim ke API atau proses lanjutan
} else {
console.log("Tanggal belum dipilih");
}
if (selectedEndDate) { const newData = {
console.log("Tanggal yang dipilih:", selectedEndDate); ...data,
console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString()); tanggal: new Date(selectedDate as any).toISOString(),
// Kirim ke API atau proses lanjutan tanggalSelesai: new Date(selectedEndDate as any).toISOString(),
} else { authorId: user?.id,
console.log("Tanggal berakhir belum dipilih"); };
}
console.log("Data berhasil disimpan", JSON.stringify(newData, null, 2));
const response = await apiEventCreate(newData);
console.log("Response", JSON.stringify(response, null, 2));
console.log("Data berhasil disimpan");
router.navigate("/event/status"); router.navigate("/event/status");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
setIsLoading(false);
} }
}; };
const buttonSubmit = ( const buttonSubmit = (
<ButtonCustom title="Simpan" onPress={handlerSubmit} /> <ButtonCustom
// <BoxButtonOnFooter> isLoading={isLoading}
// </BoxButtonOnFooter> title="Simpan"
onPress={handlerSubmit}
/>
); );
return ( return (
@@ -61,17 +134,27 @@ export default function EventCreate() {
placeholder="Masukkan nama event" placeholder="Masukkan nama event"
label="Nama Event" label="Nama Event"
required required
onChangeText={(value: any) => setData({ ...data, title: value })}
/> />
<SelectCustom <SelectCustom
label="Tipe Event" label="Tipe Event"
placeholder="Pilih tipe event" placeholder="Pilih tipe event"
data={masterTypeEvent} data={listTypeEvent.map((item: any) => ({
onChange={(value) => console.log(value)} label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value: any) =>
setData({ ...data, eventMaster_TipeAcaraId: value })
}
/> />
<TextInputCustom <TextInputCustom
label="Lokasi" label="Lokasi"
placeholder="Masukkan lokasi event" placeholder="Masukkan lokasi event"
required required
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/> />
<DateTimePickerCustom <DateTimePickerCustom
@@ -84,14 +167,24 @@ export default function EventCreate() {
minimumDate={new Date(Date.now())} minimumDate={new Date(Date.now())}
/> />
<StackCustom gap={0}>
<DateTimePickerCustom <DateTimePickerCustom
disabled={!selectedDate}
label="Tanggal & Waktu Berakhir" label="Tanggal & Waktu Berakhir"
required required
onChange={(date: Date) => { onChange={(date: Date) => {
setSelectedEndDate(date as any); setSelectedEndDate(date as any);
}} }}
value={selectedEndDate as any} value={selectedEndDate as any}
minimumDate={new Date(selectedDate as any)}
/> />
{!selectedDate && (
<TextCustom color="gray" size={"small"}>
Note: Pilih tanggal mulai terlebih dahulu
</TextCustom>
)}
<Spacing />
</StackCustom>
<TextAreaCustom <TextAreaCustom
label="Deskripsi" label="Deskripsi"
@@ -99,6 +192,9 @@ export default function EventCreate() {
required required
showCount showCount
maxLength={100} maxLength={100}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
/> />
{buttonSubmit} {buttonSubmit}

View File

@@ -1,37 +1,98 @@
import { import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
LoaderCustom,
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import { apiForumGetOne, apiForumUpdate } from "@/service/api-client/api-forum";
import { useState } from "react"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumEdit() { export default function ForumEdit() {
const { id } = useLocalSearchParams();
const [text, setText] = useState(""); const [text, setText] = useState("");
const [loadingGetData, setLoadingGetData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const buttonFooter = ( useFocusEffect(
<BoxButtonOnFooter> useCallback(() => {
<ButtonCustom onLoadData(id as string);
onPress={() => { }, [id])
console.log("Posting", text); );
const onLoadData = async (id: string) => {
try {
setLoadingGetData(true);
const response = await apiForumGetOne({ id });
setText(response.data.diskusi);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlerUpdateData = async () => {
if (!text) {
Toast.show({
type: "error",
text1: "Harap masukkan diskusi",
});
return;
}
try {
setIsLoading(true);
const response = await apiForumUpdate({
id: id as string,
data: text,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil diupdate",
});
router.back(); router.back();
}} }
> } catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = () => {
return (
<>
{!loadingGetData && (
<BoxButtonOnFooter>
<ButtonCustom isLoading={isLoading} onPress={handlerUpdateData}>
Update Update
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
)}
</>
); );
};
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter()}>
{!loadingGetData ? (
<TextAreaCustom <TextAreaCustom
placeholder="Ketik diskusi anda..." placeholder="Ketik diskusi anda..."
maxLength={1000} maxLength={1000}
showCount showCount
value={text} value={text}
onChangeText={setText} onChangeText={(value) => {
setText(value);
}}
/> />
) : (
<LoaderCustom />
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,35 +1,86 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom, AvatarComp,
AvatarCustom,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
DrawerCustom, DrawerCustom,
FloatingButton,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda"; import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { useLocalSearchParams } from "expo-router"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { useState } from "react"; import { apiUser } from "@/service/api-client/api-user";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forumku() { export default function Forumku() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false); const [listData, setListData] = useState<any | null>(null);
const [deleteAlert, setDeleteAlert] = useState(false); const [dataUser, setDataUser] = useState<any | null>(null);
const [loadingGetList, setLoadingGetList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataProfile(id as string);
}, [id])
);
const onLoadDataProfile = async (id: string) => {
try {
const response = await apiUser(id);
setDataUser(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
}
};
const onLoadData = async () => {
try {
setLoadingGetList(true);
const response = await apiForumGetAll({
search: "",
authorId: id as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetList(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper
floatingButton={
user?.id === id && (
<FloatingButton
onPress={() =>
router.navigate("/(application)/(user)/forum/create")
}
/>
)
}
>
<StackCustom> <StackCustom>
<CenterCustom> <CenterCustom>
<AvatarCustom <AvatarComp
href={`/(application)/(image)/preview-image/${id}`} fileId={dataUser?.Profile?.imageId}
href={`/(application)/(image)/preview-image/${dataUser?.Profile?.imageId}`}
size="xl" size="xl"
/> />
</CenterCustom> </CenterCustom>
@@ -37,32 +88,43 @@ export default function Forumku() {
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<TextCustom bold truncate> <TextCustom bold truncate>
@bagas_banuna @{dataUser?.username || "-"}
</TextCustom> </TextCustom>
<TextCustom>1 postingan</TextCustom> <TextCustom>{listData?.length || "0"} postingan</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<ButtonCustom href={`/profile/${id}`}> <ButtonCustom href={`/profile/${dataUser?.Profile?.id}`}>
Kunjungi Profile Kunjungi Profile
</ButtonCustom> </ButtonCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
{listDummyDiscussionForum.map((e, i) => ( {loadingGetList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom> Tidak ada diskusi</TextCustom>
) : (
<>
{listData?.map((item: any, index: number) => (
<Forum_BoxDetailSection <Forum_BoxDetailSection
key={i} isRightComponent={false}
data={e} key={index}
setOpenDrawer={setOpenDrawer} data={item}
setStatus={setStatus}
isTruncate={true} isTruncate={true}
href={`/forum/${id}`} href={`/forum/${item.id}`}
onSetData={(value) => {
setOpenDrawer(value.setOpenDrawer);
setStatus(value.setStatus);
}}
/> />
))} ))}
</>
)}
</StackCustom> </StackCustom>
</ViewWrapper> </ViewWrapper>
{/* Drawer Komponen Eksternal */} {/* Drawer Komponen Eksternal */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
@@ -72,42 +134,9 @@ export default function Forumku() {
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert} authorId={id as string}
setShowAlertStatus={setAlertStatus}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Komponen Eksternal */}
<AlertCustom
isVisible={alertStatus}
onLeftPress={() => setAlertStatus(false)}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -1,53 +1,171 @@
import { import {
AlertCustom,
ButtonCustom, ButtonCustom,
DrawerCustom, DrawerCustom,
LoaderCustom,
Spacing, Spacing,
TextAreaCustom, TextAreaCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection"; import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda"; import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar"; import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import { router, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; apiForumCreateComment,
apiForumGetComment,
apiForumGetOne,
apiForumUpdateStatus,
} from "@/service/api-client/api-forum";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
interface CommentProps {
id: string;
isActive: boolean;
komentar: string;
createdAt: Date;
authorId: string;
Author: {
id: string;
username: string;
Profile: {
id: string;
imageId: string;
};
};
}
export default function ForumDetail() { export default function ForumDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log(id); const { user } = useAuth();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
const [listComment, setListComment] = useState<CommentProps[] | null>(null);
const [isLoadingComment, setLoadingComment] = useState(false);
// Status
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const [text, setText] = useState(""); const [text, setText] = useState("");
const [authorId, setAuthorId] = useState("");
const [dataId, setDataId] = useState("");
// Comentar // Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false); const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false); const [commentId, setCommentId] = useState("");
const [commentAuthorId, setCommentAuthorId] = useState("");
const dataDummy = { useFocusEffect(
name: "Bagas", useCallback(() => {
status: "Open", onLoadData(id as string);
date: "14/07/2025", }, [id])
deskripsi: );
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae inventore iure pariatur, libero omnis excepturi. Ullam ad officiis deleniti quos esse odit nesciunt, ipsam adipisci cumque aliquam corporis culpa fugit?",
jumlahBalas: 2, const onLoadData = async (id: string) => {
try {
const response = await apiForumGetOne({ id });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
useEffect(() => {
onLoadListComment(id as string);
}, [id]);
const onLoadListComment = async (id: string) => {
try {
const response = await apiForumGetComment({
id: id as string,
});
setListComment(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
// Update Status
const handlerUpdateStatus = async (value: any) => {
try {
const response = await apiForumUpdateStatus({
id: id as string,
data: value,
});
if (response.success) {
setStatus(response.data);
setData({
...data,
ForumMaster_StatusPosting: {
status: response.data,
},
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
// Create Commentar
const handlerCreateCommentar = async () => {
const newData = {
comment: text,
authorId: user?.id,
};
try {
setLoadingComment(true);
const response = await apiForumCreateComment({
id: id as string,
data: newData,
});
if (response.success) {
setText("");
const newComment = {
id: response.data.id,
isActive: response.data.isActive,
komentar: response.data.komentar,
createdAt: response.data.createdAt,
authorId: response.data.authorId,
Author: response.data.Author,
};
setListComment((prev) => [newComment, ...(prev || [])]);
setData({
...data,
count: data.count + 1,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingComment(false);
}
}; };
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{/* <StackCustom> {!data && !listComment ? (
</StackCustom> */} <LoaderCustom />
) : (
<>
{/* Box Posting */}
<Forum_BoxDetailSection <Forum_BoxDetailSection
data={dataDummy} data={data}
setOpenDrawer={setOpenDrawer} onSetData={() => {
setStatus={setStatus} setOpenDrawer(true);
setStatus(data.ForumMaster_StatusPosting?.status);
setAuthorId(data.Author?.id);
setDataId(data.id);
}}
/> />
{/* Area Commentar */}
{data?.ForumMaster_StatusPosting?.status === "Open" && (
<>
<TextAreaCustom <TextAreaCustom
placeholder="Ketik diskusi anda..." placeholder="Ketik diskusi anda..."
maxLength={1000} maxLength={1000}
@@ -59,118 +177,87 @@ export default function ForumDetail() {
}} }}
/> />
<ButtonCustom <ButtonCustom
isLoading={isLoadingComment}
style={{ style={{
alignSelf: "flex-end", alignSelf: "flex-end",
}} }}
onPress={() => { onPress={() => {
console.log("Posting", text); handlerCreateCommentar();
router.back();
}} }}
> >
Balas Balas
</ButtonCustom> </ButtonCustom>
</>
)}
<Spacing height={40} /> <Spacing height={40} />
{listDummyCommentarForum.map((e, i) => ( {/* List Commentar */}
{_.isEmpty(listComment) ? (
<TextCustom align="center" color="gray" size={"small"}>
Tidak ada komentar
</TextCustom>
) : (
<TextCustom color="gray">Komentar :</TextCustom>
)}
<Spacing height={5} />
{listComment?.map((item: any, index: number) => (
<Forum_CommentarBoxSection <Forum_CommentarBoxSection
key={i} key={index}
data={e} data={item}
setOpenDrawer={setOpenDrawerCommentar} onSetData={(value) => {
setCommentId(value.setCommentId);
setOpenDrawerCommentar(value.setOpenDrawer);
setCommentAuthorId(value.setCommentAuthorId);
}}
/> />
))} ))}
</>
)}
</ViewWrapper> </ViewWrapper>
{/* Posting Drawer */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={id as string} id={dataId}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert} authorId={authorId}
setShowAlertStatus={setAlertStatus} handlerUpdateStatus={(value: any) => {
handlerUpdateStatus(value);
}}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Status */} {/* Commentar Drawer */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
{/* Commentar */}
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawerCommentar} isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)} closeDrawer={() => setOpenDrawerCommentar(false)}
> >
<Forum_MenuDrawerCommentar <Forum_MenuDrawerCommentar
id={id as string} id={commentId as string}
commentId={commentId}
commentAuthorId={commentAuthorId}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawerCommentar(false); setOpenDrawerCommentar(false);
}} }}
setShowDeleteAlert={setAlertDeleteCommentar} listComment={listComment}
setListComment={setListComment}
countComment={data?.count}
setCountComment={(val: any) => {
setData((prev: any) => ({
...prev,
count: val,
}));
}}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Delete Commentar */}
<AlertCustom
isVisible={alertDeleteCommentar}
title="Hapus Komentar"
message="Apakah Anda yakin ingin menghapus komentar ini?"
onLeftPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawerCommentar(false);
setAlertDeleteCommentar(false);
console.log("Hapus commentar");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -5,27 +5,69 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportCommentar } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumOtherReportCommentar() { export default function ForumOtherReportCommentar() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [value, setValue] = useState<string>("");
const handlerSubmitReport = async () => {
const newData = {
authorId: user?.id,
description: value,
};
try {
const response = await apiForumCreateReportCommentar({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
const handleSubmit = ( const handleSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={!value}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report lainnya"); handlerSubmitReport();
router.back();
}} }}
> >
Report Report
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<> <>
<ViewWrapper footerComponent={handleSubmit}> <ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Komentar" /> <TextAreaCustom
placeholder="Laporkan Komentar"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -5,17 +5,54 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { apiForumCreateReportPosting } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumOtherReportPosting() { export default function ForumOtherReportPosting() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [value, setValue] = useState<string>("");
const handlerSubmitReport = async () => {
const newData = {
authorId: user?.id,
description: value,
};
try {
const response = await apiForumCreateReportPosting({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
const handleSubmit = ( const handleSubmit = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
disabled={!value}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report lainnya"); handlerSubmitReport();
router.back();
}} }}
> >
Report Report
@@ -25,7 +62,11 @@ export default function ForumOtherReportPosting() {
return ( return (
<> <>
<ViewWrapper footerComponent={handleSubmit}> <ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Diskusi" /> <TextAreaCustom
placeholder="Laporkan Diskusi"
value={value}
onChangeText={setValue}
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,25 +1,89 @@
import { import {
ButtonCustom, ButtonCustom,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router"; import { apiForumCreateReportCommentar, apiMasterForumReportList } from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useState, useEffect } from "react";
import Toast from "react-native-toast-message";
export default function ForumReportCommentar() { export default function ForumReportCommentar() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [selectReport, setSelectReport] = useState<string>("");
const [listMaster, setListMaster] = useState<any[] | null>(null);
const [isLoadingList, setIsLoadingList] = useState(false);
useEffect(() => {
onLoadListMaster();
}, []);
const onLoadListMaster = async () => {
try {
setIsLoadingList(true);
const response = await apiMasterForumReportList();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingList(false);
}
};
const handlerReport = async () => {
const newData = {
authorId: user?.id,
categoryId: selectReport,
};
try {
const response = await apiForumCreateReportCommentar({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{isLoadingList ? (
<LoaderCustom />
) : (
<StackCustom> <StackCustom>
<Forum_ReportListSection /> <Forum_ReportListSection
listMaster={listMaster}
selectReport={selectReport}
setSelectReport={setSelectReport}
/>
<ButtonCustom <ButtonCustom
disabled={!selectReport}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report"); handlerReport();
router.back();
}} }}
> >
Report Report
@@ -28,14 +92,14 @@ export default function ForumReportCommentar() {
backgroundColor={AccentColor.blue} backgroundColor={AccentColor.blue}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Lainnya"); router.replace(`/forum/${id}/other-report-commentar`);
router.replace("/forum/[id]/other-report-commentar");
}} }}
> >
Lainnya Lainnya
</ButtonCustom> </ButtonCustom>
<Spacing/> <Spacing />
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,20 +1,103 @@
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components"; import {
import { MainColor, AccentColor } from "@/constants/color-palet"; AlertDefaultSystem,
ButtonCustom,
LoaderCustom,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection"; import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router"; import {
apiForumCreateReportPosting,
apiMasterForumReportList,
} from "@/service/api-client/api-master";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumReportPosting() { export default function ForumReportPosting() {
const { id } = useLocalSearchParams();
const { user } = useAuth();
const [selectReport, setSelectReport] = useState<string>("");
const [listMaster, setListMaster] = useState<any[] | null>(null);
const [isLoadingList, setIsLoadingList] = useState(false);
useEffect(() => {
onLoadListMaster();
}, []);
const onLoadListMaster = async () => {
try {
setIsLoadingList(true);
const response = await apiMasterForumReportList();
setListMaster(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadingList(false);
}
};
const handlerReport = async () => {
const newData = {
authorId: user?.id,
categoryId: selectReport,
};
try {
const response = await apiForumCreateReportPosting({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Laporan berhasil dikirim",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal",
text2: "Laporan gagal dikirim",
});
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
{isLoadingList ? (
<LoaderCustom />
) : (
<StackCustom> <StackCustom>
<Forum_ReportListSection /> <Forum_ReportListSection
listMaster={listMaster}
selectReport={selectReport}
setSelectReport={setSelectReport}
/>
<ButtonCustom <ButtonCustom
disabled={!selectReport}
backgroundColor={MainColor.red} backgroundColor={MainColor.red}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Report"); AlertDefaultSystem({
router.back(); title: "Laporan Posting",
message: "Apakah anda yakin ingin melaporkan postingan ini?",
textLeft: "Batal",
textRight: "Laporkan",
onPressRight: () => {
handlerReport();
},
});
}} }}
> >
Report Report
@@ -23,14 +106,14 @@ export default function ForumReportPosting() {
backgroundColor={AccentColor.blue} backgroundColor={AccentColor.blue}
textColor={MainColor.white} textColor={MainColor.white}
onPress={() => { onPress={() => {
console.log("Lainnya"); router.replace(`/forum/${id}/other-report-posting`);
router.replace("/forum/[id]/other-report-posting");
}} }}
> >
Lainnya Lainnya
</ButtonCustom> </ButtonCustom>
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -4,18 +4,47 @@ import {
TextAreaCustom, TextAreaCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { apiForumCreate } from "@/service/api-client/api-forum";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import Toast from "react-native-toast-message";
export default function ForumCreate() { export default function ForumCreate() {
const { user } = useAuth();
const [text, setText] = useState(""); const [text, setText] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => {
const newData = {
diskusi: text,
authorId: user?.id,
};
try {
setIsLoading(true);
const response = await apiForumCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Posting berhasil",
});
setText("");
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
onPress={() => { onPress={() => {
console.log("Posting", text); handlerSubmit();
router.back();
}} }}
> >
Posting Posting

View File

@@ -1,25 +1,58 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertCustom, AvatarComp,
AvatarCustom,
BackButton, BackButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
SearchInput, SearchInput,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import FloatingButton from "@/components/Button/FloatingButton"; import FloatingButton from "@/components/Button/FloatingButton";
import { MainColor } from "@/constants/color-palet"; import { useAuth } from "@/hooks/use-auth";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection"; import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda"; import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { router, Stack } from "expo-router"; import { apiForumGetAll } from "@/service/api-client/api-forum";
import { useState } from "react"; import { apiUser } from "@/service/api-client/api-user";
import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Forum() { export default function Forum() {
const id = "test-id-forum";
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [alertStatus, setAlertStatus] = useState(false); const { user } = useAuth();
const [deleteAlert, setDeleteAlert] = useState(false); const [dataUser, setDataUser] = useState<any>();
const [listData, setListData] = useState<any[]>();
const [loadingGetList, setLoadingGetList] = useState(false);
const [search, setSearch] = useState("");
const [dataId, setDataId] = useState("");
const [authorId, setAuthorId] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
onLoadDataProfile(user?.id as string);
}, [user?.id, search])
);
const onLoadDataProfile = async (id: string) => {
const response = await apiUser(id);
setDataUser(response.data);
};
const onLoadData = async () => {
try {
setLoadingGetList(true);
const response = await apiForumGetAll({ search: search });
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetList(false);
}
};
return ( return (
<> <>
@@ -27,12 +60,23 @@ export default function Forum() {
options={{ options={{
title: "Forum", title: "Forum",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <AvatarCustom href={`/forum/${id}/forumku`} />, headerRight: () => (
<AvatarComp
fileId={dataUser?.Profile?.imageId}
size="base"
href={`/forum/${user?.id}/forumku`}
/>
),
}} }}
/> />
<ViewWrapper <ViewWrapper
headerComponent={<SearchInput placeholder="Cari topik diskusi" />} headerComponent={
<SearchInput
placeholder="Cari topik diskusi"
onChangeText={(e) => setSearch(e)}
/>
}
floatingButton={ floatingButton={
<FloatingButton <FloatingButton
onPress={() => onPress={() =>
@@ -41,73 +85,45 @@ export default function Forum() {
/> />
} }
> >
{listDummyDiscussionForum.map((e, i) => ( {loadingGetList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center" color="gray">
Tidak ada diskusi
</TextCustom>
) : (
listData?.map((e: any, i: number) => (
<Forum_BoxDetailSection <Forum_BoxDetailSection
key={i} key={i}
data={e} data={e}
setOpenDrawer={setOpenDrawer} onSetData={() => {
setStatus={setStatus} setDataId(e.id);
setOpenDrawer(true);
setStatus(e.ForumMaster_StatusPosting?.status);
setAuthorId(e.Author?.id);
}}
isTruncate={true} isTruncate={true}
href={`/forum/${id}`} href={`/forum/${e.id}`}
isRightComponent={false}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom
height={350} height={"auto"}
isVisible={openDrawer} isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)} closeDrawer={() => setOpenDrawer(false)}
> >
<Forum_MenuDrawerBerandaSection <Forum_MenuDrawerBerandaSection
id={id} id={dataId}
authorId={authorId}
status={status} status={status}
setIsDrawerOpen={() => { setIsDrawerOpen={() => {
setOpenDrawer(false); setOpenDrawer(false);
}} }}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Status */}
<AlertCustom
isVisible={alertStatus}
title="Ubah Status Forum"
message="Apakah Anda yakin ingin mengubah status forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setAlertStatus(false);
console.log("Ubah status forum");
}}
textLeft="Batal"
textRight="Ubah"
colorRight={MainColor.green}
/>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
title="Hapus Forum"
message="Apakah Anda yakin ingin menghapus forum ini?"
onLeftPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Batal");
}}
onRightPress={() => {
setOpenDrawer(false);
setDeleteAlert(false);
console.log("Hapus forum");
}}
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</> </>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { StackCustom, ViewWrapper } from "@/components"; import { StackCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
@@ -8,28 +9,40 @@ import TabSection from "@/screens/Home/tabSection";
import { tabsHome } from "@/screens/Home/tabsList"; import { tabsHome } from "@/screens/Home/tabsList";
import Home_FeatureSection from "@/screens/Home/topFeatureSection"; import Home_FeatureSection from "@/screens/Home/topFeatureSection";
import { apiUser } from "@/service/api-client/api-user"; import { apiUser } from "@/service/api-client/api-user";
import { apiVersion } from "@/service/api-config";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { Redirect, router, Stack } from "expo-router"; import { Redirect, router, Stack } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
export default function Application() { export default function Application() {
const { user } = useAuth(); const { token, user } = useAuth();
const [data, setData] = useState<any>({});
const [data, setData] = useState<any>();
useEffect(() => { useEffect(() => {
onLoadData(); onLoadData();
checkVersion();
}, []); }, []);
async function onLoadData() { async function onLoadData() {
const response = await apiUser(user?.id as string); const response = await apiUser(user?.id as string);
console.log("Response profile >>", JSON.stringify(response?.data?.Profile, null, 2));
setData(response.data); setData(response.data);
} }
const checkVersion = async () => {
const response = await apiVersion();
console.log("Version >>", JSON.stringify(response.data, null, 2));
};
if (data && data?.active === false) { if (data && data?.active === false) {
console.log("User is not active");
return <Redirect href={`/waiting-room`} />; return <Redirect href={`/waiting-room`} />;
} }
if (data && data?.Profile === null) { if (data && data?.Profile === null) {
console.log("Profile is null");
return <Redirect href={`/profile/create`} />; return <Redirect href={`/profile/create`} />;
} }
@@ -37,7 +50,7 @@ export default function Application() {
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "HIPMI", title: `HIPMI`,
headerLeft: () => ( headerLeft: () => (
<Ionicons <Ionicons
name="search" name="search"

View File

@@ -2,18 +2,46 @@ import {
BaseBox, BaseBox,
FloatingButton, FloatingButton,
Grid, Grid,
LoaderCustom,
ProgressCustom, ProgressCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DUMMY_IMAGE from "@/constants/dummy-image-value"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { apiInvestmentGetAll } from "@/service/api-client/api-investment";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function InvestmentBursa() { export default function InvestmentBursa() {
const [list, setList] = useState<any[] | null>(null);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [])
);
const onLoadList = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetAll();
console.log("[DATA LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
return ( return (
<ViewWrapper <ViewWrapper
hideFooter hideFooter
@@ -21,12 +49,26 @@ export default function InvestmentBursa() {
<FloatingButton onPress={() => router.push("/investment/create")} /> <FloatingButton onPress={() => router.push("/investment/create")} />
} }
> >
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<BaseBox key={index} paddingTop={7} paddingBottom={7} href={`/investment/${index}`}> <LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom>Belum ada data</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
href={`/investment/${item.id}`}
>
<Grid> <Grid>
<Grid.Col span={5}> <Grid.Col span={5}>
<Image <Image
source={DUMMY_IMAGE.background} source={
item && item.imageId
? API_STRORAGE.GET({ fileId: item.imageId })
: DUMMY_IMAGE.background
}
style={{ width: "auto", height: 100, borderRadius: 10 }} style={{ width: "auto", height: 100, borderRadius: 10 }}
/> />
</Grid.Col> </Grid.Col>
@@ -35,26 +77,49 @@ export default function InvestmentBursa() {
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<StackCustom> <StackCustom>
<TextCustom truncate={2}> <TextCustom truncate={2}>{item.title}</TextCustom>
Title here : Lorem ipsum dolor sit amet consectetur <ProgressCustom
adipisicing elit. Omnis, exercitationem, sequi enim quod label={`${item.progress}%`}
distinctio maiores laudantium amet, quidem atque repellat sit value={item.progress}
vitae qui aliquam est veritatis laborum eum voluptatum totam! size="lg"
/>
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days") <=
0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 5,
}}
>
<Ionicons
name="alert-circle-outline"
size={16}
color="red"
/>
<TextCustom color="red" size="small">
Periode Investasi Selesai
</TextCustom> </TextCustom>
<ProgressCustom value={index % 5 * 20} size="lg" /> </View>
) : (
<TextCustom> <TextCustom>
Sisa waktu: {dayjs().diff(dayjs(), "day")} hari Sisa waktu:{" "}
{Number(item?.pencarianInvestor) -
dayjs().diff(dayjs(item.countDown), "days")}{" "}
hari
</TextCustom> </TextCustom>
)}
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }
// <View style={{ padding: 20, gap: 16 }}> // <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom> // <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" /> // <ProgressCustom value={70} color="primary" size="md" />

View File

@@ -1,13 +1,48 @@
import { ScrollableCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
LoaderCustom,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import Investment_StatusBox from "@/screens/Invesment/StatusBox"; import Investment_StatusBox from "@/screens/Invesment/StatusBox";
import { useState } from "react"; import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentPortofolio() { export default function InvestmentPortofolio() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "publish"
); );
const [listData, setListData] = useState<any[]>([]);
const [loadingList, setLoadingList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [user?.id, activeCategory])
);
const onLoadData = async () => {
try {
setLoadingList(true);
const response = await apiInvestmentGetByStatus({
authorId: user?.id as string,
status: activeCategory as string,
});
setListData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingList(false);
}
};
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb. // tambahkan logika lain seperti filter dsb.
@@ -26,14 +61,20 @@ export default function InvestmentPortofolio() {
); );
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => ( {loadingList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Investment_StatusBox <Investment_StatusBox
key={index} key={index}
id={index.toString()} data={item}
status={activeCategory as string} status={activeCategory as string}
href={`/investment/${index}/${activeCategory}/detail`} href={`/investment/${item.id}/${activeCategory}/detail`}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,58 +1,99 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BadgeCustom, BadgeCustom,
BaseBox, BaseBox,
Grid, Grid,
LoaderCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { dummyMasterStatusTransaction } from "@/lib/dummy-data/_master/status-transaction"; import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs"; import { formatChatTime } from "@/utils/formatChatTime";
import { router } from "expo-router"; import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
export default function InvestmentTransaction() { export default function InvestmentTransaction() {
const randomStatusData = Array.from({ length: 10 }, () => { const { user } = useAuth();
const randomIndex = Math.floor( const [list, setList] = useState<any>([]);
Math.random() * dummyMasterStatusTransaction.length const [loadList, setLoadList] = useState<boolean>(false);
);
return dummyMasterStatusTransaction[randomIndex];
});
const handlePress = (value: string) => { useFocusEffect(
if (value === "menunggu") { useCallback(() => {
router.push(`/investment/${value}/(transaction-flow)/invoice`); onLoadList();
} else if (value === "proses") { }, [user?.id])
router.push(`/investment/${value}/(transaction-flow)/process`); );
} else if (value === "berhasil") {
router.push(`/investment/${value}/(transaction-flow)/success`); const onLoadList = async () => {
} else if (value === "gagal") { try {
router.push(`/investment/${value}/(transaction-flow)/failed`); setLoadList(true);
const response = await apiInvestmentGetInvoice({
authorId: user?.id as string,
category: "transaction",
});
console.log("[RESPONSE LIST]", JSON.stringify(response.data, null, 2));
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
const handlerColor = (status: string) => {
if (status === "menunggu") {
return "orange";
} else if (status === "proses") {
return "white";
} else if (status === "berhasil") {
return "green";
} else if (status === "gagal") {
return "red";
}
};
const handlePress = ({ id, status }: { id: string; status: string }) => {
if (status === "menunggu") {
router.push(`/investment/${id}/(transaction-flow)/invoice`);
} else if (status === "proses") {
router.push(`/investment/${id}/(transaction-flow)/process`);
} else if (status === "berhasil") {
router.push(`/investment/${id}/(transaction-flow)/success`);
} else if (status === "gagal") {
router.push(`/investment/${id}/(transaction-flow)/failed`);
} }
}; };
return ( return (
<ViewWrapper hideFooter> <ViewWrapper hideFooter>
{randomStatusData.map((item, i) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom>Tidak ada data</TextCustom>
) : (
list.map((item: any, i: number) => (
<BaseBox <BaseBox
key={i} key={i}
paddingTop={7} paddingTop={7}
paddingBottom={7} paddingBottom={7}
onPress={() => { onPress={() => {
handlePress(item.value); handlePress({
id: item.id,
status: _.lowerCase(item.statusInvoice),
});
}} }}
> >
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextCustom truncate> <TextCustom truncate>{item?.title || "-"}</TextCustom>
Title Investment: Lorem ipsum dolor sit amet consectetur
adipisicing elit. Am culpa excepturi deleniti soluta animi
porro amet ducimus.
</TextCustom>
<TextCustom color="gray" size="small"> <TextCustom color="gray" size="small">
{dayjs().format("DD/MM/YYYY")} {formatChatTime(item?.createdAt)}
</TextCustom> </TextCustom>
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>
@@ -62,20 +103,21 @@ export default function InvestmentTransaction() {
<Grid.Col span={5} style={{ alignItems: "flex-end" }}> <Grid.Col span={5} style={{ alignItems: "flex-end" }}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextCustom bold truncate> <TextCustom bold truncate>
Rp. 7.500.000 Rp. {formatCurrencyDisplay(item?.nominal) || "-"}
</TextCustom> </TextCustom>
<BadgeCustom <BadgeCustom
variant="light" variant="light"
color={item.color} color={handlerColor(_.lowerCase(item.statusInvoice))}
style={GStyles.alignSelfFlexEnd} style={GStyles.alignSelfFlexEnd}
> >
{item.label} {item?.statusInvoice || "-"}
</BadgeCustom> </BadgeCustom>
</StackCustom> </StackCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -7,17 +7,78 @@ import {
InformationBox, InformationBox,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiInvestmentUpsertDocument } from "@/service/api-client/api-investment";
import { uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { FontAwesome5 } from "@expo/vector-icons"; import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentAddDocument() { export default function InvestmentAddDocument() {
const { id } = useLocalSearchParams();
const [data, setData] = useState({
title: "",
});
const [pdf, setPdf] = useState<any>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handlerSubmit = async () => {
try {
setIsLoading(true);
const responseUploadFile = await uploadFileService({
dirId: DIRECTORY_ID.investasi_dokumen,
imageUri: pdf.uri,
});
if (!responseUploadFile.success) {
throw new Error(responseUploadFile.message);
}
const newData = {
title: data.title,
fileId: responseUploadFile.data.id,
};
const response = await apiInvestmentUpsertDocument({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil disimpan",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal menyimpan data",
});
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom> <ButtonCustom
isLoading={isLoading}
disabled={!pdf || data.title.length <= 0}
onPress={handlerSubmit}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -31,22 +92,33 @@ export default function InvestmentAddDocument() {
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukan judul dokumen" placeholder="Masukan judul dokumen"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
{pdf ? (
<TextCustom truncate>{pdf.name}</TextCustom>
) : (
<FontAwesome5 <FontAwesome5
name="file-pdf" name="file-pdf"
size={30} size={30}
color={MainColor.disabled} color={MainColor.disabled}
/> />
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => onPress={() =>
router.push("/(application)/(image)/take-picture/123") pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
} }
> >
Upload Upload

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -7,17 +8,99 @@ import {
InformationBox, InformationBox,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import DIRECTORY_ID from "@/constants/directory-id";
import { FontAwesome5 } from "@expo/vector-icons"; import {
import { router } from "expo-router"; apiInvestmentGetDocument,
apiInvestmentUpsertDocument,
} from "@/service/api-client/api-investment";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import pickFile from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentEditDocument() { export default function InvestmentEditDocument() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [pdf, setPdf] = useState<any>(null);
const [titleFile, setTitleFile] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetDocument({
id: id as string,
category: "one-document",
});
setData(response.data);
setTitleFile(response.data.title);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerUpdate = async () => {
const prevFileId = data.fileId;
try {
setIsLoading(true);
const responseUploadFile = await uploadFileService({
dirId: DIRECTORY_ID.investasi_dokumen,
imageUri: pdf.uri,
});
if (!responseUploadFile.success) {
throw new Error(responseUploadFile.message);
}
const newData = {
title: data.title,
fileId: responseUploadFile.data.id,
};
const response = await apiInvestmentUpsertDocument({
id: id as string,
data: newData,
});
if (response.success) {
const delPrevFile = await deleteFileService({
id: prevFileId,
});
console.log("[DEL PREV FILE]", delPrevFile);
Toast.show({
type: "success",
text1: "Data berhasil diupdate",
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal mengupdate data",
});
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} disabled={!pdf} onPress={handlerUpdate}>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -31,22 +114,29 @@ export default function InvestmentEditDocument() {
label="Judul Dokumen" label="Judul Dokumen"
placeholder="Masukan judul dokumen" placeholder="Masukan judul dokumen"
required required
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {pdf ? (
name="file-pdf" <TextCustom truncate>{pdf.name}</TextCustom>
size={30} ) : (
color={MainColor.disabled} <TextCustom truncate>{_.snakeCase(titleFile || "")}.pdf</TextCustom>
/> )}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => onPress={() =>
router.push("/(application)/(image)/take-picture/123") pickFile({
allowedType: "pdf",
setPdfUri(file) {
setPdf(file);
},
})
} }
> >
Upload Upload

View File

@@ -1,16 +1,58 @@
import { ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import { apiInvestmentGetDocument } from "@/service/api-client/api-investment";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentListOfDocument() { export default function InvestmentListOfDocument() {
const { id } = useLocalSearchParams();
console.log("ID >> ", id);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadListDocument();
}, [id])
);
const onLoadListDocument = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetDocument({
id: id as string,
category: "all-document",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument <Investment_BoxDetailDocument
key={index} key={index}
title={`Judul Dokumen ${index + 1}`} title={item.title}
href={`/(file)/${index + 1}`} href={`/(file)/${item.fileId}`}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,90 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import {
apiInvestmentDeleteDocument,
apiInvestmentGetDocument,
} from "@/service/api-client/api-investment";
import { AntDesign, Ionicons } from "@expo/vector-icons"; import { AntDesign, Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentRecapOfDocument() { export default function InvestmentRecapOfDocument() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerBox, setOpenDrawerBox] = useState(false); const [openDrawerBox, setOpenDrawerBox] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
const [selectId, setSelectId] = useState<string | null>(null);
useFocusEffect(
useCallback(() => {
onLoadListDocument();
}, [id])
);
const onLoadListDocument = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetDocument({
id: id as string,
category: "all-document",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
setList([]);
} finally {
setLoadList(false);
}
};
const handlerDeleteDocument = async () => {
try {
const response = await apiInvestmentDeleteDocument({
id: selectId as string,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Data berhasil dihapus",
});
setList((prev: any[] | null) => {
if (!prev) return null;
return prev.filter((item: any) => item.id !== selectId);
});
setOpenDrawerBox(false);
setSelectId(null);
}
} catch (error) {
console.log("[ERROR]", error);
Toast.show({
type: "error",
text1: "Gagal menghapus data",
});
}
};
return ( return (
<> <>
@@ -37,10 +104,17 @@ export default function InvestmentRecapOfDocument() {
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<Investment_BoxDetailDocument <Investment_BoxDetailDocument
key={index} key={index}
title={`Judul Dokumen ${index + 1}`} title={item.title}
leftIcon={ leftIcon={
<Ionicons <Ionicons
name="ellipsis-horizontal-outline" name="ellipsis-horizontal-outline"
@@ -50,12 +124,16 @@ export default function InvestmentRecapOfDocument() {
zIndex: 10, zIndex: 10,
alignSelf: "flex-end", alignSelf: "flex-end",
}} }}
onPress={() => setOpenDrawerBox(true)} onPress={() => {
setSelectId(item.id);
setOpenDrawerBox(true);
}}
/> />
} }
href={`/(file)/${id}`} href={`/(file)/${item.fileId}`}
/> />
))} ))
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer On Header */} {/* Drawer On Header */}
@@ -69,7 +147,7 @@ export default function InvestmentRecapOfDocument() {
{ {
icon: ( icon: (
<AntDesign <AntDesign
name="pluscircle" name="plus-circle"
size={ICON_SIZE_SMALL} size={ICON_SIZE_SMALL}
color={MainColor.white} color={MainColor.white}
/> />
@@ -96,7 +174,7 @@ export default function InvestmentRecapOfDocument() {
{ {
icon: <IconEdit />, icon: <IconEdit />,
label: "Edit Dokumen", label: "Edit Dokumen",
path: `/investment/${id}/(document)/edit-document`, path: `/investment/${selectId}/(document)/edit-document`,
}, },
{ {
icon: ( icon: (
@@ -119,12 +197,14 @@ export default function InvestmentRecapOfDocument() {
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: () => {
setOpenDrawerBox(false); handlerDeleteDocument();
}, },
}); });
} } else {
router.push(item.path as any); router.push(item.path as any);
setOpenDrawer(false); }
setOpenDrawerBox(false);
}} }}
/> />
</DrawerCustom> </DrawerCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem, AlertDefaultSystem,
BackButton, BackButton,
@@ -8,37 +9,71 @@ import {
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { IconTrash } from "@/components/_Icon/IconTrash"; import { IconTrash } from "@/components/_Icon/IconTrash";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { useAuth } from "@/hooks/use-auth";
import { useState } from "react"; import {
apiInvestmentDeleteNews,
apiInvestmentGetNews,
} from "@/service/api-client/api-investment";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentNews() { export default function InvestmentNews() {
const { id, news } = useLocalSearchParams(); const { user } = useAuth();
const { news } = useLocalSearchParams();
const id = news as string;
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [data, setData] = useState<any | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetNews({
id: id,
category: "one-news",
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Detail Berita", title: "Detail Berita",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, headerRight: () =>
user?.id === data?.authorId && (
<DotButton onPress={() => setOpenDrawer(true)} />
),
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<DummyLandscapeImage /> {data && data?.imageId && (
<DummyLandscapeImage imageId={data?.imageId || ""} />
)}
<TextCustom bold align="center" size="large"> <TextCustom bold align="center" size="large">
Judul Berita {news} Terbaru {(data && data?.title) || "-"}
</TextCustom>
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Laborum
fuga mollitia laboriosam voluptatibus quos molestias, illo fugiat
esse repellat, ad officia earum numquam? Aliquid corrupti quam
tempora cum harum est!
</TextCustom> </TextCustom>
<TextCustom>{(data && data?.deskripsi) || "-"}</TextCustom>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
</ViewWrapper> </ViewWrapper>
@@ -52,7 +87,7 @@ export default function InvestmentNews() {
data={[ data={[
{ {
label: "Hapus Berita", label: "Hapus Berita",
path: `/investment/${id}/add-news`, path: ``,
icon: <IconTrash />, icon: <IconTrash />,
color: "red", color: "red",
}, },
@@ -63,9 +98,26 @@ export default function InvestmentNews() {
message: "Apakah Anda yakin ingin menghapus berita ini?", message: "Apakah Anda yakin ingin menghapus berita ini?",
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: () => { onPressRight: async () => {
try {
const response = await apiInvestmentDeleteNews({ id });
if (response.success) {
Toast.show({
type: "success",
text1: "Berita berhasil dihapus",
});
router.back(); router.back();
setOpenDrawer(false); setOpenDrawer(false);
} else {
Toast.show({
type: "error",
text1: "Gagal menghapus berita",
});
}
} catch (error) {
console.log("[ERROR]", error);
}
}, },
}); });
}} }}

View File

@@ -9,17 +9,89 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiInvestmentCreateNews } from "@/service/api-client/api-investment";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentAddNews() { export default function InvestmentAddNews() {
const { id } = useLocalSearchParams();
const [image, setImage] = useState<IFileData | null>(null);
const [data, setData] = useState({
title: "",
deskripsi: "",
});
const [isLoading, setIsLoading] = useState(false);
const handlerSubmit = async () => {
let imageId = "";
if (!data.title || !data.deskripsi) {
Toast.show({
type: "error",
text1: "Judul dan deskripsi harus diisi",
});
return;
}
try {
setIsLoading(true);
if (image) {
const uploadImage = await uploadFileService({
dirId: DIRECTORY_ID.investasi_berita,
imageUri: image.uri,
});
imageId = uploadImage.data.id;
}
const newData = {
id: id as string,
title: data.title,
deskripsi: data.deskripsi,
imageId: imageId,
};
const response = await apiInvestmentCreateNews({
id: id as string,
data: newData,
});
if (response.success) {
Toast.show({
type: "success",
text1: "Berita berhasil disimpan",
});
router.back();
} else {
Toast.show({
type: "error",
text1: "Gagal menyimpan berita",
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Pengunggahan foto ke aplikasi bersifat opsional dan tidak diwajibkan, Anda dapat menyimpan berita tanpa mengunggah foto." /> <InformationBox text="Pengunggahan foto ke aplikasi bersifat opsional dan tidak diwajibkan, Anda dapat menyimpan berita tanpa mengunggah foto." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -30,6 +102,8 @@ export default function InvestmentAddNews() {
label="Judul Berita" label="Judul Berita"
placeholder="Masukan judul berita" placeholder="Masukan judul berita"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
label="Deskripsi Berita" label="Deskripsi Berita"
@@ -37,13 +111,11 @@ export default function InvestmentAddNews() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={handlerSubmit}>
onPress={() => {
router.back();
}}
>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -1,18 +1,51 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom, TextCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentListOfNews() { export default function InvestmentListOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetNews({
id: id as string,
category: "all-news",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -22,16 +55,25 @@ export default function InvestmentListOfNews() {
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />, // headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}} }}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox <BaseBox
key={index} key={index}
paddingBlock={5} paddingBlock={5}
href={`/investment/${id}/(news)/${index + 1}`} href={`/investment/[id]/(news)/${item.id}`}
> >
<TextCustom bold>Berita Terbaru {index + 1}</TextCustom> <TextCustom bold>{item.title}</TextCustom>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -1,19 +1,53 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
BaseBox, BaseBox,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { apiInvestmentGetNews } from "@/service/api-client/api-investment";
import { useState } from "react"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function InvestmentRecapOfNews() { export default function InvestmentRecapOfNews() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const [list, setList] = useState<any[] | null>(null);
const [loadList, setLoadList] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setLoadList(true);
const response = await apiInvestmentGetNews({
id: id as string,
category: "all-news",
});
setList(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<> <>
<Stack.Screen <Stack.Screen
@@ -24,15 +58,23 @@ export default function InvestmentRecapOfNews() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 15 }).map((_, index) => ( {loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox <BaseBox
key={index} key={index}
paddingBlock={5} paddingBlock={5}
href={`/investment/${id}/(news)/${index + 1}`} href={`/investment/[id]/(news)/${item.id}`}
> >
<TextCustom bold>Berita Terbaru {index + 1}</TextCustom> <TextCustom bold>{item.title}</TextCustom>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -9,16 +10,89 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router, useLocalSearchParams } from "expo-router"; import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function InvestmentInvest() { export default function InvestmentInvest() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
// console.log("[ID]", id);
const [data, setData] = useState<any>(null);
const [jumlah, setJumlah] = useState<number>(0);
const [total, setTotal] = useState<number>(0);
const [sisaLembar, setSisaLembar] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetOne({
id: id as string,
});
setData(response.data);
setSisaLembar(response.data?.sisaLembar);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleTextChange = (text: string) => {
// Izinkan input kosong → anggap sebagai 0 (atau abaikan, tergantung UX)
if (text === "") {
setJumlah(0);
setTotal(0);
return;
}
// Regex: hanya digit (angka bulat positif)
const integerRegex = /^\d+$/;
if (integerRegex.test(text)) {
const numValue = Number(text);
// Karena regex sudah pastikan hanya angka, isNaN biasanya false
// Tapi tetap aman untuk cek
if (!isNaN(numValue)) {
setJumlah(numValue);
setTotal(numValue * Number(data?.hargaLembar));
console.log("[VALUE]", numValue);
}
}
// Jika input tidak valid (misal: "12a", "12."), abaikan → state tidak berubah
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.push(`/investment/${id}/select-bank`)}>Beli</ButtonCustom> <ButtonCustom
isLoading={isLoading}
disabled={jumlah < 10 || jumlah > sisaLembar}
onPress={async () => {
try {
setIsLoading(true);
await AsyncStorage.setItem(
LOCAL_STORAGE_KEY.transactionInvestment,
JSON.stringify({ jumlah, total })
);
router.push(`/investment/${id}/select-bank`);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
}}
>
Beli
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
</> </>
); );
@@ -34,7 +108,7 @@ export default function InvestmentInvest() {
<TextCustom>Sisa Lembar Saham</TextCustom> <TextCustom>Sisa Lembar Saham</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>3.000</TextCustom> <TextCustom>{data?.sisaLembar || "-"}</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>
@@ -42,7 +116,7 @@ export default function InvestmentInvest() {
<TextCustom>Harga Per Lembar</TextCustom> <TextCustom>Harga Per Lembar</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>Rp. 1.000</TextCustom> <TextCustom>{data?.hargaLembar || "-"}</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Grid> <Grid>
@@ -69,6 +143,10 @@ export default function InvestmentInvest() {
}} }}
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
value={jumlah.toString()}
onChangeText={(value) => {
handleTextChange(value);
}}
/> />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
@@ -78,7 +156,7 @@ export default function InvestmentInvest() {
<TextCustom>Total Harga</TextCustom> <TextCustom>Total Harga</TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}> <Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom>Rp. 1.000</TextCustom> <TextCustom> Rp. {formatCurrencyDisplay(total)}</TextCustom>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</StackCustom> </StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
@@ -9,11 +10,96 @@ import {
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import CopyButton from "@/components/Button/CoyButton";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { router, useLocalSearchParams } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import {
apiInvestmentGetInvoice,
apiInvestmentUpdateInvoice,
} from "@/service/api-client/api-investment";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { View } from "react-native";
import Toast from "react-native-toast-message";
export default function InvestmentInvoice() { export default function InvestmentInvoice() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("[ID]", id);
const [data, setData] = useState<any>({});
const [image, setImage] = useState<IFileData>({
name: "",
uri: "",
size: 0,
});
const [isLoading, setIsLoading] = useState<boolean>(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetInvoice({
id: id as string,
category: "invoice",
});
console.log("[RES INVOICE]", JSON.stringify(response.data, null, 2));
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlerSubmitUpdate = async () => {
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
dirId: DIRECTORY_ID.investasi_bukti_transfer,
imageUri: image?.uri,
});
console.log("[RESPONSE UPLOAD IMAGE]", responseUploadImage);
if (!responseUploadImage?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah bukti transfer",
});
return;
}
const response = await apiInvestmentUpdateInvoice({
id: id as string,
data: {
imageId: responseUploadImage?.data?.id,
},
status: "proses",
});
if (response.success) {
console.log(
"[RESPONSE UPDATE]",
JSON.stringify(response.data, null, 2)
);
Toast.show({
type: "success",
text1: "Berhasil mengunggah bukti transfer",
});
router.push(`/investment/${id}/(transaction-flow)/process`);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<> <>
<ViewWrapper> <ViewWrapper>
@@ -21,9 +107,23 @@ export default function InvestmentInvoice() {
<InformationBox text="Mohon transfer ke rekening dibawah" /> <InformationBox text="Mohon transfer ke rekening dibawah" />
<BaseBox> <BaseBox>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextCustom>Nama BANK</TextCustom> <Grid>
<TextCustom>Nama Penerima</TextCustom> <Grid.Col span={4}>
<TextCustom>Bank</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data?.MasterBank?.namaBank}</TextCustom>
</Grid.Col>
</Grid>
<Spacing height={10} /> <Spacing height={10} />
<Grid>
<Grid.Col span={4}>
<TextCustom>Nama Akun</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{data?.MasterBank?.namaAkun}</TextCustom>
</Grid.Col>
</Grid>
<BaseBox backgroundColor={MainColor.soft_darkblue}> <BaseBox backgroundColor={MainColor.soft_darkblue}>
<Grid containerStyle={{ justifyContent: "center" }}> <Grid containerStyle={{ justifyContent: "center" }}>
@@ -34,7 +134,7 @@ export default function InvestmentInvoice() {
}} }}
> >
<TextCustom size="xlarge" bold color="yellow"> <TextCustom size="xlarge" bold color="yellow">
4567898765433567 {data?.MasterBank?.norek}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -43,7 +143,7 @@ export default function InvestmentInvoice() {
alignItems: "flex-end", alignItems: "flex-end",
}} }}
> >
<ButtonCustom>Salin</ButtonCustom> <CopyButton textToCopy={data?.MasterBank?.norek} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
@@ -65,7 +165,7 @@ export default function InvestmentInvoice() {
}} }}
> >
<TextCustom size="xlarge" bold color="yellow"> <TextCustom size="xlarge" bold color="yellow">
Rp. 1.000.000 Rp. {formatCurrencyDisplay(data?.nominal)}
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -74,7 +174,7 @@ export default function InvestmentInvoice() {
alignItems: "flex-end", alignItems: "flex-end",
}} }}
> >
<ButtonCustom>Salin</ButtonCustom> <CopyButton textToCopy={data?.nominal} />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox>
@@ -83,10 +183,37 @@ export default function InvestmentInvoice() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom>Upload bukti transfer anda.</TextCustom> <TextCustom align="center">
Upload bukti transfer anda.
</TextCustom>
{image ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 10,
paddingInline: 20,
}}
>
<TextCustom bold align="center" truncate>
{image?.name}
</TextCustom>
</View>
) : (
<TextCustom align="center">
Tidak ada gambar yang diunggah
</TextCustom>
)}
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickFile({
allowedType: "image",
setImageUri(file: any) {
console.log("[IMAGE]", file);
setImage(file);
},
});
}} }}
icon="upload" icon="upload"
> >
@@ -96,14 +223,16 @@ export default function InvestmentInvoice() {
</BaseBox> </BaseBox>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
disabled={!image}
onPress={() => { onPress={() => {
router.push(`/investment/${id}/(transaction-flow)/process`); handlerSubmitUpdate();
}} }}
> >
Saya Sudah Transfer Saya Sudah Transfer
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>
<Spacing/> <Spacing />
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -1,13 +1,6 @@
import { import { BaseBox, StackCustom, TextCustom, ViewWrapper } from "@/components";
BaseBox, import MoneyTransferAnimation from "@/components/_ShareComponent/MoneyTransferAnimation";
Grid, import { View } from "react-native";
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { ActivityIndicator } from "react-native";
export default function InvestmentProcess() { export default function InvestmentProcess() {
return ( return (
@@ -16,21 +9,24 @@ export default function InvestmentProcess() {
<BaseBox> <BaseBox>
<StackCustom> <StackCustom>
<TextCustom align="center" bold> <TextCustom align="center" bold>
Admin sedang memproses transaksi investasimu Admin sedang memvalidasi data dan bukti transfer anda. Mohon
tunggu proses ini selesai.
</TextCustom> </TextCustom>
<ActivityIndicator size="large" color={MainColor.yellow} /> <View style={{ alignItems: "center", justifyContent: "center" }}>
<MoneyTransferAnimation />
</View>
</StackCustom> </StackCustom>
</BaseBox> </BaseBox>
<BaseBox> {/* <BaseBox>
<Grid> <Grid>
<Grid.Col span={10} style={{justifyContent: 'center'}}> <Grid.Col span={10} style={{ justifyContent: "center" }}>
<TextCustom size="small"> <TextCustom size="small">
Hubungi admin jika tidak kunjung di proses! Klik pada logo Hubungi admin jika tidak kunjung di proses! Klik pada logo
Whatsapp ini. Whatsapp ini.
</TextCustom> </TextCustom>
</Grid.Col> </Grid.Col>
<Grid.Col span={2} style={{alignItems: "flex-end"}}> <Grid.Col span={2} style={{ alignItems: "flex-end" }}>
<Ionicons <Ionicons
name="logo-whatsapp" name="logo-whatsapp"
size={50} size={50}
@@ -38,7 +34,7 @@ export default function InvestmentProcess() {
/> />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</BaseBox> </BaseBox> */}
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -5,33 +5,95 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom"; import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { dummyMasterBank } from "@/lib/dummy-data/_master/bank"; import { LOCAL_STORAGE_KEY } from "@/constants/local-storage-key";
import { useAuth } from "@/hooks/use-auth";
import { apiInvestmentCreateInvoice } from "@/service/api-client/api-investment";
import { apiMasterBank } from "@/service/api-client/api-master";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useEffect, useState } from "react";
export default function InvestmentSelectBank() { export default function InvestmentSelectBank() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [value, setValue] = useState<any | number>(""); const [select, setSelect] = useState<any | number>("");
const [listBank, setListBank] = useState<any>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
loadListBank();
}, []);
const loadListBank = async () => {
try {
const response = await apiMasterBank();
setListBank(response.data);
} catch (error) {
console.log("[ERROR]", error);
setListBank([]);
}
};
const handlerSubmit = async () => {
try {
setIsLoading(true);
const dataCheckout = await AsyncStorage.getItem(
LOCAL_STORAGE_KEY.transactionInvestment
);
if (dataCheckout) {
const storage = JSON.parse(dataCheckout);
const newData = {
...storage,
bankId: select,
authorId: user?.id,
};
const response = await apiInvestmentCreateInvoice({
id: id as string,
data: newData,
});
if (response.success) {
console.log("[RESPONSE >>]", response);
const invoiceId = response.data.id;
const delStorage = await AsyncStorage.removeItem(
LOCAL_STORAGE_KEY.transactionInvestment
);
console.log("[DEL STORAGE]", delStorage);
router.replace(`/investment/${invoiceId}/invoice`);
} else {
console.log("[FAILED]", response);
}
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={() => handlerSubmit()}>Pilih</ButtonCustom>
onPress={() => router.replace(`/investment/${id}/invoice`)}
>
Pilih
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
</> </>
); );
}; };
return ( return (
<ViewWrapper footerComponent={buttonSubmit()}> <ViewWrapper footerComponent={buttonSubmit()}>
<RadioGroup value={value} onChange={setValue}> <RadioGroup value={select} onChange={setSelect}>
{dummyMasterBank.map((item) => ( {_.isEmpty(listBank)
<BaseBox key={item.name}> ? []
<RadioCustom label={item.name} value={item.code} /> : listBank?.map((item: any) => (
<BaseBox key={item.id}>
<RadioCustom label={item.namaBank} value={item.id} />
</BaseBox> </BaseBox>
))} ))}
</RadioGroup> </RadioGroup>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
@@ -9,19 +10,47 @@ import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value"; import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection"; import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useState } from "react"; import { useCallback, useState } from "react";
export default function InvestmentDetailStatus() { export default function InvestmentDetailStatus() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false); const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, status])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetOne({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
router.navigate(item.path as any); router.navigate(item.path as any);
@@ -36,13 +65,14 @@ export default function InvestmentDetailStatus() {
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} id={data?.id}
prospectusId={data?.prospektusFileId}
status={status as string} status={status as string}
/> />
); );
const buttonSection = ( const buttonSection = (
<Investment_ButtonInvestasiSection id={id as string} isMine={false} /> <Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
); );
return ( return (
@@ -63,6 +93,7 @@ export default function InvestmentDetailStatus() {
<ViewWrapper> <ViewWrapper>
<Invesment_DetailDataPublishSection <Invesment_DetailDataPublishSection
status={status as string} status={status as string}
data={data}
bottomSection={bottomSection} bottomSection={bottomSection}
buttonSection={buttonSection} buttonSection={buttonSection}
/> />

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
@@ -5,38 +6,149 @@ import {
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
InformationBox, InformationBox,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import DIRECTORY_ID from "@/constants/directory-id";
import { FontAwesome5 } from "@expo/vector-icons"; import {
import { router } from "expo-router"; apiInvestmentGetOne,
apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentEditProspectus() { export default function InvestmentEditProspectus() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [loadingGet, setLoadingGet] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [pdf, setPdf] = useState<IFileData | null>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setLoadingGet(true);
const response = await apiInvestmentGetOne({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGet(false);
}
};
const handleSubmitUpdate = async () => {
const prevProspectusFileId = data?.prospektusFileId;
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
imageUri: pdf?.uri as any,
dirId: DIRECTORY_ID.investasi_prospektus,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const prospektusFileId = responseUploadImage.data.id;
const responseUpdate = await apiInvestmentUpdateData({
id: id as string,
data: prospektusFileId,
category: "prospectus",
});
if (responseUpdate.success) {
const deletePrevImage = await deleteFileService({
id: prevProspectusFileId as any,
});
if (!deletePrevImage.success) {
console.log("[ERROR DELETE PREV IMAGE]", deletePrevImage.message);
return;
}
Toast.show({
type: "success",
text1: "Data berhasil diupdate",
});
router.back();
} else {
Toast.show({
type: "error",
text1: responseUpdate.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom
disabled={pdf === null}
isLoading={isLoading}
onPress={handleSubmitUpdate}
>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={!loadingGet && buttonFooter}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." /> <InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." />
<Spacing /> <Spacing />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
<FontAwesome5 {loadingGet ? (
name="file-pdf" <LoaderCustom />
size={30} ) : pdf ? (
color={MainColor.disabled} <TextCustom truncate>{pdf.name}</TextCustom>
/> ) : (
<TextCustom truncate>
{_.snakeCase(data?.title || "").replace(/_/g, "-")}.pdf
</TextCustom>
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
disabled={loadingGet}
icon="upload" icon="upload"
onPress={() => router.push("/(application)/(image)/take-picture/123")} onPress={() => {
pickFile({
allowedType: "pdf",
setPdfUri(file: any) {
setPdf({
uri: file.uri,
name: file.name,
size: file.size,
});
},
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>

View File

@@ -1,40 +1,224 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden"; import API_STRORAGE from "@/constants/base-url-api-strorage";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor"; import DIRECTORY_ID from "@/constants/directory-id";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden"; import {
import { router } from "expo-router"; apiInvestmentGetOne,
import { useState } from "react"; apiInvestmentUpdateData,
} from "@/service/api-client/api-investment";
import { apiMasterInvestment } from "@/service/api-client/api-master";
import {
deleteFileService,
uploadFileService,
} from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
interface IInvestment {
title?: string;
targetDana?: string;
hargaLembar?: string;
totalLembar?: string;
roi?: string;
masterPencarianInvestorId?: string;
masterPeriodeDevidenId?: string;
masterPembagianDevidenId?: string;
authorId?: string;
imageId?: string;
prospektusFileId?: string;
}
export default function InvestmentEdit() { export default function InvestmentEdit() {
const [data, setData] = useState({ const { id } = useLocalSearchParams();
const [data, setData] = useState<IInvestment>({
title: "", title: "",
targetDana: 0, targetDana: "",
hargaPerLembar: 0, hargaLembar: "",
totalLembar: 0, totalLembar: "",
rasioKeuntungan: 0, roi: "",
pencarianInvestor: "", masterPencarianInvestorId: "",
periodeDeviden: "", masterPeriodeDevidenId: "",
pembagianDeviden: "", masterPembagianDevidenId: "",
authorId: "",
imageId: "",
prospektusFileId: "",
}); });
const [image, setImage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [loadingMaster, setLoadingMaster] = useState(false);
const [listPencarianInvestor, setListPencarianInvestor] = useState<any[]>([]);
const [listPeriodeDeviden, setListPeriodeDeviden] = useState<any[]>([]);
const [listPembagianDeviden, setListPembagianDeviden] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadMaster();
onLoadData();
}, [id])
);
const onLoadMaster = async () => {
try {
setLoadingMaster(true);
const response = await apiMasterInvestment({ category: "" });
setListPencarianInvestor(response.data.pencarianInvestor);
setListPeriodeDeviden(response.data.periodeDeviden);
setListPembagianDeviden(response.data.pembagianDeviden);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
};
const onLoadData = async () => {
try {
const response = await apiInvestmentGetOne({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const displayTargetDana = formatCurrencyDisplay(data?.targetDana);
const displayHargaPerLembar = formatCurrencyDisplay(data?.hargaLembar);
const realTotalLembar = Number(data?.targetDana) / Number(data?.hargaLembar);
const displayTotalLembar = formatCurrencyDisplay(realTotalLembar);
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
const numeric = text.replace(/\D/g, "");
setData((prev) => ({ ...prev, [field]: numeric }));
};
const validateData = () => {
if (
!data.title ||
!data.targetDana ||
!data.hargaLembar ||
!data.totalLembar ||
!data.roi ||
!data.masterPencarianInvestorId ||
!data.masterPeriodeDevidenId ||
!data.masterPembagianDevidenId
) {
Toast.show({
type: "info",
text1: "Harap isi semua data",
});
return false;
}
return true;
};
const handleSubmitUpdate = async () => {
let newData = {
...data,
totalLembar: realTotalLembar.toString(),
};
if (!validateData()) {
return;
}
try {
setIsLoading(true);
if (image) {
const responseUploadImage = await uploadFileService({
imageUri: image,
dirId: DIRECTORY_ID.investasi_image,
});
if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const deletePrevImage = await deleteFileService({
id: data?.imageId as any,
});
if (!deletePrevImage.success) {
Toast.show({
type: "error",
text1: "Gagal menghapus gambar",
});
return;
}
newData = {
...newData,
imageId: responseUploadImage.data.id,
};
}
const responseUpdate = await apiInvestmentUpdateData({
id: id as string,
data: newData,
category: "data"
});
if (responseUpdate.success) {
Toast.show({
type: "success",
text1: "Data berhasil diupdate",
});
router.back();
} else {
Toast.show({
type: "error",
text1: responseUpdate.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." /> <InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={
image ? image : API_STRORAGE.GET({ fileId: data?.imageId as any })
}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
@@ -47,7 +231,7 @@ export default function InvestmentEdit() {
required required
placeholder="Judul" placeholder="Judul"
label="Judul" label="Judul"
value={data.title} value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })} onChangeText={(value) => setData({ ...data, title: value })}
/> />
@@ -57,22 +241,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Target Dana" label="Target Dana"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("targetDana")}
setData({ ...data, targetDana: Number(value) }) value={displayTargetDana}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/>
<TextInputCustom
required
iconLeft="Rp."
placeholder="0"
label="Target Dana"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -81,10 +251,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Harga Per Lembar" label="Harga Per Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("hargaLembar")}
setData({ ...data, targetDana: Number(value) }) value={displayHargaPerLembar}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -92,10 +260,8 @@ export default function InvestmentEdit() {
placeholder="0" placeholder="0"
label="Total Lembar" label="Total Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, totalLembar: value })}
setData({ ...data, totalLembar: Number(value) }) value={displayTotalLembar}
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -104,57 +270,78 @@ export default function InvestmentEdit() {
label="Rasio Keuntungan / ROI %" label="Rasio Keuntungan / ROI %"
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, roi: value })}
setData({ ...data, rasioKeuntungan: Number(value) }) value={data?.roi === "" ? "" : data?.roi}
}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
}
/> />
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
required required
placeholder="Pilih batas waktu" placeholder="Pilih batas waktu"
label="Pencarian Investor" label="Pencarian Investor"
data={dummyListPencarianInvestor.map((item) => ({ data={
_.isEmpty(listPencarianInvestor)
? []
: listPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`, label: item.name + `${" "}hari`,
value: item.id, value: item.id,
}))} }))
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
} }
value={data.pencarianInvestor} onChange={(value) =>
setData({ ...data, masterPencarianInvestorId: value as any })
}
value={data.masterPencarianInvestorId}
/> />
)}
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
required required
placeholder="Pilih batas waktu" placeholder="Pilih batas waktu"
label="Pilih Periode Deviden" label="Pilih Periode Deviden"
data={dummyPeriodeDeviden.map((item) => ({ data={
_.isEmpty(listPeriodeDeviden)
? []
: listPeriodeDeviden.map((item) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))
onChange={(value) =>
setData({ ...data, periodeDeviden: value as any })
} }
value={data.periodeDeviden} onChange={(value) =>
setData({ ...data, masterPeriodeDevidenId: value as any })
}
value={data.masterPeriodeDevidenId}
/> />
)}
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
required required
placeholder="Pilih batas waktu" placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden" label="Pilih Pembagian Deviden"
data={dummyPembagianDeviden.map((item) => ({ data={
_.isEmpty(listPembagianDeviden)
? []
: listPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`, label: item.name + `${" "}bulan`,
value: item.id, value: item.id,
}))} }))
onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any })
} }
value={data.pembagianDeviden} onChange={(value) =>
setData({ ...data, masterPembagianDevidenId: value as any })
}
value={data.masterPembagianDevidenId}
/> />
)}
<Spacing /> <Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}> <ButtonCustom isLoading={isLoading} onPress={handleSubmitUpdate}>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
@@ -9,18 +10,45 @@ import { IconDocument, IconEdit, IconNews } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM } from "@/constants/constans-value"; import { ICON_SIZE_MEDIUM } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection"; import Investment_ButtonInvestasiSection from "@/screens/Invesment/ButtonInvestasiSection";
import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail"; import Invesment_ComponentBoxOnBottomDetail from "@/screens/Invesment/ComponentBoxOnBottomDetail";
import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection"; import Invesment_DetailDataPublishSection from "@/screens/Invesment/DetailDataPublishSection";
import { apiInvestmentGetOne } from "@/service/api-client/api-investment";
import { AntDesign, MaterialIcons } from "@expo/vector-icons"; import { AntDesign, MaterialIcons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useState } from "react"; import { useCallback, useState } from "react";
export default function InvestmentDetail() { export default function InvestmentDetail() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false); const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const [data, setData] = useState<any>(null);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id, status])
);
const onLoadData = async () => {
try {
const response = await apiInvestmentGetOne({
id: id as string,
});
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
}
};
const handlePressDraft = (item: IMenuDrawerItem) => { const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -37,11 +65,14 @@ export default function InvestmentDetail() {
const bottomSection = ( const bottomSection = (
<Invesment_ComponentBoxOnBottomDetail <Invesment_ComponentBoxOnBottomDetail
id={id as string} id={id as string}
status={'publish'} prospectusId={data?.prospektusFileId}
status={"publish"}
/> />
); );
const buttonSection = <Investment_ButtonInvestasiSection id={id as string} isMine={true} />; const buttonSection = (
<Investment_ButtonInvestasiSection id={id as string} isMine={user?.id === data?.author?.id} />
);
return ( return (
<> <>
@@ -61,6 +92,7 @@ export default function InvestmentDetail() {
<ViewWrapper> <ViewWrapper>
<Invesment_DetailDataPublishSection <Invesment_DetailDataPublishSection
status={"publish"} status={"publish"}
data={data}
bottomSection={bottomSection} bottomSection={bottomSection}
buttonSection={buttonSection} buttonSection={buttonSection}
/> />

View File

@@ -5,72 +5,231 @@ import {
CenterCustom, CenterCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden"; import DIRECTORY_ID from "@/constants/directory-id";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor"; import { useAuth } from "@/hooks/use-auth";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden"; import { apiInvestmentCreate } from "@/service/api-client/api-investment";
import { apiMasterInvestment } from "@/service/api-client/api-master";
import { uploadFileService } from "@/service/upload-service";
import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay";
import pickFile, { IFileData } from "@/utils/pickFile";
import { FontAwesome5 } from "@expo/vector-icons"; import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function InvestmentCreate() { export default function InvestmentCreate() {
const { user } = useAuth();
const [data, setData] = useState({ const [data, setData] = useState({
title: "", title: "",
targetDana: 0, targetDana: "",
hargaPerLembar: 0, hargaPerLembar: "",
totalLembar: 0, totalLembar: "",
rasioKeuntungan: 0, rasioKeuntungan: "",
pencarianInvestor: "", pencarianInvestor: "",
periodeDeviden: "", periodeDeviden: "",
pembagianDeviden: "", pembagianDeviden: "",
authorId: "",
imageId: "",
prospektusFileId: "",
});
const [image, setImage] = useState<string | null>(null);
const [pdf, setPdf] = useState<IFileData | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [loadingMaster, setLoadingMaster] = useState(false);
const [listPencarianInvestor, setListPencarianInvestor] = useState<any[]>([]);
const [listPeriodeDeviden, setListPeriodeDeviden] = useState<any[]>([]);
const [listPembagianDeviden, setListPembagianDeviden] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadMaster();
}, [])
);
const onLoadMaster = async () => {
try {
setLoadingMaster(true);
const response = await apiMasterInvestment({ category: "" });
setListPencarianInvestor(response.data.pencarianInvestor);
setListPeriodeDeviden(response.data.periodeDeviden);
setListPembagianDeviden(response.data.pembagianDeviden);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingMaster(false);
}
};
const displayTargetDana = formatCurrencyDisplay(data.targetDana);
const displayHargaPerLembar = formatCurrencyDisplay(data.hargaPerLembar);
const realTotalLembar = Number(data.targetDana) / Number(data.hargaPerLembar);
const displayTotalLembar = formatCurrencyDisplay(realTotalLembar);
const handleChangeCurrency = (field: keyof typeof data) => (text: string) => {
const numeric = text.replace(/\D/g, "");
setData((prev) => ({ ...prev, [field]: numeric }));
};
const validateData = () => {
if (
!data.title ||
!data.targetDana ||
!data.hargaPerLembar ||
!data.rasioKeuntungan ||
!data.pencarianInvestor ||
!data.periodeDeviden ||
!data.pembagianDeviden
) {
Toast.show({
type: "error",
text1: "Harap isi semua data",
});
return false;
}
return true;
};
const handleSubmit = async () => {
if (!validateData()) {
return;
}
if (!image || !pdf) {
Toast.show({
type: "error",
text1: "Harap upload gambar dan file PDF",
});
return;
}
try {
setIsLoading(true);
const responseUploadImage = await uploadFileService({
imageUri: image,
dirId: DIRECTORY_ID.investasi_image,
}); });
// const [coba, setCoba] = useState(""); if (!responseUploadImage.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUploadImage.data.id;
const responseUploadPdf = await uploadFileService({
imageUri: pdf.uri,
dirId: DIRECTORY_ID.investasi_prospektus,
});
if (!responseUploadPdf.success) {
Toast.show({
type: "error",
text1: "Gagal mengunggah file PDF",
});
return;
}
const pdfId = responseUploadPdf.data.id;
const newData = {
title: data.title,
targetDana: data.targetDana,
hargaLembar: data.hargaPerLembar,
totalLembar: realTotalLembar.toString(),
roi: data.rasioKeuntungan,
masterPencarianInvestorId: data.pencarianInvestor,
masterPembagianDevidenId: data.pembagianDeviden,
masterPeriodeDevidenId: data.periodeDeviden,
authorId: user?.id,
imageId: imageId,
prospektusFileId: pdfId,
};
const response = await apiInvestmentCreate({ data: newData });
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: response.message,
});
router.replace("/investment/portofolio");
} else {
Toast.show({
type: "error",
text1: "Info",
text2: response.message,
});
}
} catch (error) {
console.log("error", error);
} finally {
setIsLoading(false);
}
};
// const [coba, setCoba] = useState("");
return ( return (
<ViewWrapper> <ViewWrapper>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
{/* <View style={GStyles.inputContainerInput}>
<TextInput
style={{
...GStyles.inputText,
}}
onChangeText={(value) => setCoba(value)}
value={coba}
keyboardType="decimal-pad"
/>
</View> */}
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." /> <InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setImageUri: ({ uri }) => {
setImage(uri);
},
allowedType: "image",
});
}}
> >
Upload Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing />
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya." /> <InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya. Gunakan format PDF." />
<BaseBox> <BaseBox>
<CenterCustom> <CenterCustom>
{pdf ? (
<TextCustom>{pdf.name}</TextCustom>
) : (
<FontAwesome5 <FontAwesome5
name="file-pdf" name="file-pdf"
size={30} size={30}
color={MainColor.disabled} color={MainColor.disabled}
/> />
)}
</CenterCustom> </CenterCustom>
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.push("/take-picture/1")} onPress={() => {
pickFile({
setPdfUri: ({ uri, name, size }) => {
setPdf({ uri, name, size });
},
allowedType: "pdf",
});
}}
> >
Upload File Upload File
</ButtonCenteredOnly> </ButtonCenteredOnly>
@@ -90,22 +249,8 @@ export default function InvestmentCreate() {
placeholder="0" placeholder="0"
label="Target Dana" label="Target Dana"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("targetDana")}
setData({ ...data, targetDana: Number(value) }) value={displayTargetDana}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/>
<TextInputCustom
required
iconLeft="Rp."
placeholder="0"
label="Target Dana"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<TextInputCustom <TextInputCustom
@@ -114,22 +259,24 @@ export default function InvestmentCreate() {
placeholder="0" placeholder="0"
label="Harga Per Lembar" label="Harga Per Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={handleChangeCurrency("hargaPerLembar")}
setData({ ...data, targetDana: Number(value) }) value={displayHargaPerLembar}
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/> />
<StackCustom gap={0}>
<TextInputCustom <TextInputCustom
required required
placeholder="0" placeholder="0"
label="Total Lembar" label="Total Lembar"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => // onChangeText={handleChangeCurrency("totalLembar")}
setData({ ...data, totalLembar: Number(value) }) value={displayTotalLembar}
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/> />
<TextCustom size={"small"} color="gray">
*Total lembar dihitung dari, Target Dana / Harga Perlembar
</TextCustom>
</StackCustom>
<Spacing />
<TextInputCustom <TextInputCustom
required required
@@ -137,57 +284,80 @@ export default function InvestmentCreate() {
label="Rasio Keuntungan / ROI %" label="Rasio Keuntungan / ROI %"
placeholder="0" placeholder="0"
keyboardType="numeric" keyboardType="numeric"
onChangeText={(value) => onChangeText={(value) => setData({ ...data, rasioKeuntungan: value })}
setData({ ...data, rasioKeuntungan: Number(value) })
}
value={ value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString() data.rasioKeuntungan === "" ? "" : data.rasioKeuntungan.toString()
} }
/> />
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
required required
placeholder="Pilih batas waktu" placeholder="Pilih batas waktu"
label="Pencarian Investor" label="Pencarian Investor"
data={dummyListPencarianInvestor.map((item) => ({ data={
_.isEmpty(listPencarianInvestor)
? []
: listPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`, label: item.name + `${" "}hari`,
value: item.id, value: item.id,
}))} }))
}
onChange={(value) => onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any }) setData({ ...data, pencarianInvestor: value as any })
} }
value={data.pencarianInvestor} value={data.pencarianInvestor}
/> />
)}
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
required required
placeholder="Pilih batas waktu" placeholder="Pilih batas waktu"
label="Pilih Periode Deviden" label="Pilih Periode Deviden"
data={dummyPeriodeDeviden.map((item) => ({ data={
_.isEmpty(listPeriodeDeviden)
? []
: listPeriodeDeviden.map((item) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))
}
onChange={(value) => onChange={(value) =>
setData({ ...data, periodeDeviden: value as any }) setData({ ...data, periodeDeviden: value as any })
} }
value={data.periodeDeviden} value={data.periodeDeviden}
/> />
)}
{loadingMaster ? (
<LoaderCustom />
) : (
<SelectCustom <SelectCustom
required required
placeholder="Pilih batas waktu" placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden" label="Pilih Pembagian Deviden"
data={dummyPembagianDeviden.map((item) => ({ data={
_.isEmpty(listPembagianDeviden)
? []
: listPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`, label: item.name + `${" "}bulan`,
value: item.id, value: item.id,
}))} }))
}
onChange={(value) => onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any }) setData({ ...data, pembagianDeviden: value as any })
} }
value={data.pembagianDeviden} value={data.pembagianDeviden}
/> />
)}
<Spacing /> <Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}> <ButtonCustom isLoading={isLoading} onPress={() => handleSubmit()}>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
</StackCustom> </StackCustom>

View File

@@ -1,16 +1,57 @@
import { BaseBox, TextCustom, ViewWrapper } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import { jobDataDummy } from "@/screens/Job/listDataDummy"; 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 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> <ViewWrapper hideFooter>
{jobDataDummy.map((e, i) => ( {isLoadData ? (
<BaseBox key={i} paddingTop={20} paddingBottom={20}> <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"> <TextCustom align="center" bold truncate size="large">
{e.posisi} {item?.title || "-"}
</TextCustom> </TextCustom>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -2,33 +2,82 @@ import {
AvatarUsernameAndOtherComponent, AvatarUsernameAndOtherComponent,
BoxWithHeaderSection, BoxWithHeaderSection,
FloatingButton, FloatingButton,
LoaderCustom,
SearchInput, SearchInput,
Spacing, Spacing,
StackCustom,
TextCustom, TextCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetAll } from "@/service/api-client/api-job";
import { router } from "expo-router"; 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 <ViewWrapper
hideFooter hideFooter
floatingButton={ floatingButton={
<FloatingButton onPress={() => router.push("/job/create")} /> <FloatingButton onPress={() => router.push("/job/create")} />
} }
headerComponent={<SearchInput placeholder="Cari pekerjaan" />} headerComponent={
<SearchInput placeholder="Cari pekerjaan" onChangeText={handleSearch} />
}
> >
{jobDataDummy.map((item, index) => ( {isLoadData ? (
<BoxWithHeaderSection key={index} onPress={() => router.push(`/job/${item.id}`)}> <LoaderCustom />
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${item.id}`} /> ) : _.isEmpty(listData) ? (
<Spacing /> <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"> <TextCustom truncate={2} align="center" bold size="large">
{item.posisi} {item?.title || "-"}
</TextCustom> </TextCustom>
</StackCustom>
<Spacing /> <Spacing />
</BoxWithHeaderSection> </BoxWithHeaderSection>
))} ))
)}
<Spacing />
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,17 +1,46 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox, BaseBox,
LoaderCustom,
ScrollableCustom, ScrollableCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { useAuth } from "@/hooks/use-auth";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useState } from "react"; import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function JobStatus() { export default function JobStatus() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" "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) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -32,19 +61,24 @@ export default function JobStatus() {
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <ViewWrapper headerComponent={scrollComponent} hideFooter>
{jobDataDummy.map((e, i) => ( {isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((e, i) => (
<BaseBox <BaseBox
key={i} key={i}
paddingTop={20} paddingTop={20}
paddingBottom={20} paddingBottom={20}
href={`/job/${e.id}/${activeCategory}/detail`} href={`/job/${e?.id}/${activeCategory}/detail`}
// onPress={() => console.log("pressed")}
> >
<TextCustom align="center" bold truncate size="large"> <TextCustom align="center" bold truncate size="large">
{e.posisi} {activeCategory?.toUpperCase()} {e?.title}
</TextCustom> </TextCustom>
</BaseBox> </BaseBox>
))} ))
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,23 +1,51 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BackButton, BackButton,
DotButton, DotButton,
DrawerCustom, DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid, MenuDrawerDynamicGrid,
Spacing, Spacing,
StackCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconEdit } from "@/components/_Icon"; import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types"; import { IMenuDrawerItem } from "@/components/_Interface/types";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection"; import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetOne } from "@/service/api-client/api-job";
import { router, Stack, useLocalSearchParams } from "expo-router"; import {
import { useState } from "react"; router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
export default function JobDetailStatus() { export default function JobDetailStatus() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false); const [openDrawer, setOpenDrawer] = useState(false);
const jobDetail = jobDataDummy.find((e) => e.id === Number(id)); const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetOne({ id: id as string });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handlePress = (item: IMenuDrawerItem) => { const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path); console.log("PATH >> ", item.path);
@@ -38,9 +66,23 @@ export default function JobDetailStatus() {
}} }}
/> />
<ViewWrapper> <ViewWrapper>
<Job_BoxDetailSection data={jobDetail} /> {isLoadData ? (
<Job_ButtonStatusSection status={status as string} /> <LoaderCustom />
) : (
<>
<StackCustom>
<Job_BoxDetailSection data={data} />
<Job_ButtonStatusSection
id={id as string}
status={status as string}
isLoading={isLoading}
onSetLoading={setIsLoading}
isArchive={true}
/>
</StackCustom>
<Spacing /> <Spacing />
</>
)}
</ViewWrapper> </ViewWrapper>
<DrawerCustom <DrawerCustom

View File

@@ -0,0 +1,100 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
ButtonCustom,
LoaderCustom,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import Toast from "react-native-toast-message";
export default function JobDetailArchive() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetOne({ id: id as string });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handleArchive = async () => {
try {
setIsLoading(true);
const response = await apiJobUpdateData({
id: id as string,
data: false,
category: "archive",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
router.back();
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
return (
<>
{isLoadData ? (
<LoaderCustom />
) : (
<ViewWrapper>
<>
<StackCustom>
<Job_BoxDetailSection data={data} />
<ButtonCustom
isLoading={isLoading}
onPress={() => {
handleArchive();
}}
>
Publish kembali
</ButtonCustom>
{/* <Job_ButtonStatusSection
id={id as string}
status={status as string}
isLoading={isLoading}
onSetLoading={setIsLoading}
isArchive={true}
/> */}
</StackCustom>
<Spacing />
</>
</ViewWrapper>
)}
</>
);
}

View File

@@ -1,21 +1,133 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
DummyLandscapeImage,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
LoaderCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { router } from "expo-router"; import DIRECTORY_ID from "@/constants/directory-id";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import {
deleteFileService,
uploadFileService,
} from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function JobEdit() { export default function JobEdit() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any>({
title: "",
content: "",
deskripsi: "",
});
const [isLoadData, setIsLoadData] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [imageUri, setImageUri] = useState<string | null>(null);
useEffect(() => {
onLoadData();
}, [id]);
const onLoadData = async () => {
try {
setIsLoadData(true);
const response = await apiJobGetOne({ id: id as string });
if (response.success) {
setData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
const handlerOnUpdate = async () => {
if (!data.title || !data.content || !data.deskripsi) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
let newImageId = "";
if (imageUri) {
const responseUploadImage = await uploadFileService({
imageUri: imageUri,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
newImageId = responseUploadImage.data.id;
}
}
if (data?.imageId) {
const responseDeleteImage = await deleteFileService({
id: data.imageId,
});
if (!responseDeleteImage.success) {
console.log("[ERROR DELETE IMAGE]", responseDeleteImage.message);
}
}
const newData = {
title: data.title,
content: data.content,
deskripsi: data.deskripsi,
imageId: newImageId,
};
const response = await apiJobUpdateData({
id: id as string,
data: newData,
category: "edit",
});
if (response.success) {
Toast.show({
type: "success",
text1: response.message,
});
router.back();
} else {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom> <ButtonCustom isLoading={isLoading} onPress={() => handlerOnUpdate()}>
Update
</ButtonCustom>
<Spacing /> <Spacing />
</> </>
); );
@@ -23,13 +135,25 @@ export default function JobEdit() {
return ( return (
<ViewWrapper> <ViewWrapper>
{isLoadData ? (
<LoaderCustom />
) : (
<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." />
<LandscapeFrameUploaded /> {imageUri ? (
<LandscapeFrameUploaded image={imageUri as any} />
) : (
<BaseBox>
<DummyLandscapeImage imageId={data?.imageId} />
</BaseBox>
)}
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); pickImage({
setImageUri,
});
}} }}
icon="upload" icon="upload"
> >
@@ -42,6 +166,8 @@ export default function JobEdit() {
label="Judul Lowongan" label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja" placeholder="Masukan Judul Lowongan Kerja"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -50,6 +176,8 @@ export default function JobEdit() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -58,10 +186,13 @@ export default function JobEdit() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
{buttonSubmit()} {buttonSubmit()}
</StackCustom> </StackCustom>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,25 +1,43 @@
import { /* eslint-disable react-hooks/exhaustive-deps */
ButtonCustom, import { ButtonCustom, LoaderCustom, Spacing, StackCustom, ViewWrapper } from "@/components";
Spacing,
ViewWrapper
} from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection"; import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import { jobDataDummy } from "@/screens/Job/listDataDummy"; import { apiJobGetOne } from "@/service/api-client/api-job";
import { BASE_URL } from "@/service/api-config";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard"; import * as Clipboard from "expo-clipboard";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Alert, Linking } from "react-native"; import { Alert, Linking } from "react-native";
export default function JobDetail() { export default function JobDetail() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const jobDetail = jobDataDummy.find((e) => e.id === Number(id)); useEffect(() => {
onLoadData();
}, [id]);
const OpenLinkButton = () => { const onLoadData = async () => {
const jobUrl = try {
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657"; setIsLoading(true);
const response = await apiJobGetOne({ id: id as string });
setData(response.data);
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const baseUrl = BASE_URL;
const linkUrl = `${baseUrl}/job-vacancy/`;
const OpenLinkButton = ({ id }: { id: string }) => {
const jobUrl = `${linkUrl}${id}`;
const openInBrowser = async () => { const openInBrowser = async () => {
const supported = await Linking.canOpenURL(jobUrl); const supported = await Linking.canOpenURL(jobUrl);
@@ -44,9 +62,8 @@ export default function JobDetail() {
); );
}; };
const CopyLinkButton = () => { const CopyLinkButton = ({ id }: { id: string }) => {
const jobUrl = const jobUrl = `${linkUrl}${id}`;
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657";
const copyToClipboard = async () => { const copyToClipboard = async () => {
await Clipboard.setStringAsync(jobUrl); await Clipboard.setStringAsync(jobUrl);
@@ -70,10 +87,18 @@ export default function JobDetail() {
return ( return (
<ViewWrapper> <ViewWrapper>
<Job_BoxDetailSection data={jobDetail}/> {isLoading ? (
<OpenLinkButton /> <LoaderCustom />
) : (
<>
<Job_BoxDetailSection data={data} />
<StackCustom>
<OpenLinkButton id={id as string} />
<CopyLinkButton id={id as string} />
</StackCustom>
<Spacing /> <Spacing />
<CopyLinkButton /> </>
)}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -7,19 +7,99 @@ import {
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiJobCreate } from "@/service/api-client/api-job";
import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { router } from "expo-router"; import { router } from "expo-router";
import { useState } from "react";
import Toast from "react-native-toast-message";
export default function JobCreate() { export default function JobCreate() {
const nextUrl = "/(application)/(user)/job/(tabs)/status";
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState<string | null>(null);
const [data, setData] = useState({
title: "",
content: "",
deskripsi: "",
authorId: "",
});
const handlerOnSubmit = async () => {
let imageId = "";
const newData = {
title: data.title,
content: data.content,
deskripsi: data.deskripsi,
authorId: user?.id,
imageId: "",
};
if (!data.title || !data.content || !data.deskripsi || !user?.id) {
Toast.show({
type: "info",
text1: "Info",
text2: "Harap isi semua data",
});
return;
}
try {
setIsLoading(true);
if (image === null || !image) {
const response = await apiJobCreate(newData);
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Lowongan berhasil dibuat",
});
router.replace(nextUrl);
}
return;
}
const responseUploadImage = await uploadFileService({
imageUri: image,
dirId: DIRECTORY_ID.job_image,
});
if (responseUploadImage.success) {
imageId = responseUploadImage.data.id;
}
const fixData = {
...newData,
imageId: imageId,
};
const response = await apiJobCreate(fixData);
if (response.success) {
Toast.show({
type: "success",
text1: "Berhasil",
text2: "Lowongan berhasil dibuat",
});
router.replace(nextUrl);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoading(false);
}
};
const buttonSubmit = () => { const buttonSubmit = () => {
return ( return (
<> <>
<ButtonCustom <ButtonCustom isLoading={isLoading} onPress={() => handlerOnSubmit()}>
onPress={() =>
router.replace("/(application)/(user)/job/(tabs)/status")
}
>
Simpan Simpan
</ButtonCustom> </ButtonCustom>
<Spacing /> <Spacing />
@@ -32,10 +112,19 @@ export default function JobCreate() {
<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." />
<LandscapeFrameUploaded /> {/* <BaseBox>
<Image
source={image ? { uri: image } : DUMMY_IMAGE.dummy_image}
style={{ width: "100%", height: 200 }}
/>
</BaseBox> */}
<LandscapeFrameUploaded image={image as string} />
<ButtonCenteredOnly <ButtonCenteredOnly
onPress={() => { onPress={() => {
router.push("/(application)/(image)/take-picture/123"); // router.push("/(application)/(image)/take-picture/123");
pickImage({
setImageUri: setImage,
});
}} }}
icon="upload" icon="upload"
> >
@@ -48,6 +137,8 @@ export default function JobCreate() {
label="Judul Lowongan" label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja" placeholder="Masukan Judul Lowongan Kerja"
required required
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -56,6 +147,8 @@ export default function JobCreate() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.content}
onChangeText={(value) => setData({ ...data, content: value })}
/> />
<TextAreaCustom <TextAreaCustom
@@ -64,6 +157,8 @@ export default function JobCreate() {
required required
showCount showCount
maxLength={1000} maxLength={1000}
value={data.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/> />
{buttonSubmit()} {buttonSubmit()}

View File

@@ -1,54 +1,226 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
MapCustom,
Spacing, Spacing,
TextInputCustom, TextInputCustom,
ViewWrapper ViewWrapper,
} from "@/components"; } from "@/components";
import { router, useLocalSearchParams } from "expo-router"; import API_IMAGE from "@/constants/api-storage";
import DIRECTORY_ID from "@/constants/directory-id";
import { apiMapsGetOne, apiMapsUpdate } from "@/service/api-client/api-maps";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { StyleSheet, View } from "react-native";
import MapView, { LatLng, Marker } from "react-native-maps";
import Toast from "react-native-toast-message";
const defaultRegion = {
latitude: -8.737109,
longitude: 115.1756897,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
};
export default function MapsEdit() { export default function MapsEdit() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<any | null>({
id: "",
namePin: "",
latitude: "",
longitude: "",
imageId: "",
});
const [selectedLocation, setSelectedLocation] = useState<LatLng | null>(null);
const [image, setImage] = useState<IFileData | null>(null);
const [isLoading, setLoading] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
);
const onLoadData = async () => {
try {
const response = await apiMapsGetOne({ id: id as string });
if (response.success) {
setData({
id: response.data.id,
namePin: response.data.namePin,
latitude: response.data.latitude,
longitude: response.data.longitude,
imageId: response.data.imageId,
});
}
} catch (error) {
console.log("[ERROR]", error);
}
};
const handleMapPress = (event: any) => {
const { latitude, longitude } = event.nativeEvent.coordinate;
const location = { latitude, longitude };
setSelectedLocation(location);
};
const handleSubmit = async () => {
let newData: any;
if (!data.namePin) {
Toast.show({
type: "error",
text1: "Nama pin harus diisi",
});
return;
}
newData = {
namePin: data?.namePin,
latitude: selectedLocation?.latitude || data?.latitude,
longitude: selectedLocation?.longitude || data?.longitude,
};
try {
setLoading(true);
if (image) {
const responseUpload = await uploadFileService({
dirId: DIRECTORY_ID.map_image,
imageUri: image?.uri,
});
if (!responseUpload?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUpload?.data?.id;
newData = {
namePin: data?.namePin,
latitude: selectedLocation?.latitude,
longitude: selectedLocation?.longitude,
newImageId: imageId,
};
}
const responseUpdate = await apiMapsUpdate({
id: data?.id,
data: newData,
});
if (!responseUpdate.success) {
Toast.show({
type: "error",
text1: "Gagal mengupdate map",
});
return;
}
Toast.show({
type: "success",
text1: "Map berhasil diupdate",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => { disabled={!data.namePin}
console.log(`Simpan maps ${id}`); onPress={handleSubmit}
router.back() isLoading={isLoading}
}}
> >
Simpan Update
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
return ( return (
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." /> <InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox> <View style={[styles.container, { height: 400 }]}>
<MapCustom /> <MapView
</BaseBox> style={styles.map}
initialRegion={
data?.latitude && data?.longitude
? {
latitude: data?.latitude,
longitude: data?.longitude,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
}
: defaultRegion
}
onPress={handleMapPress}
showsUserLocation={true}
showsMyLocationButton={true}
loadingEnabled={true}
loadingIndicatorColor="#666"
loadingBackgroundColor="#f0f0f0"
>
{selectedLocation ? (
<Marker
coordinate={selectedLocation}
title="Lokasi Dipilih"
description={`Lat: ${selectedLocation.latitude.toFixed(
6
)}, Lng: ${selectedLocation.longitude.toFixed(6)}`}
pinColor="red"
/>
) : (
<Marker
coordinate={defaultRegion}
title="Lokasi Dipilih"
description={`Lat: ${defaultRegion.latitude.toFixed(
6
)}, Lng: ${defaultRegion.longitude.toFixed(6)}`}
pinColor="red"
/>
)}
</MapView>
</View>
<TextInputCustom <TextInputCustom
required required
label="Nama Pin" label="Nama Pin"
placeholder="Masukkan nama pin maps" placeholder="Masukkan nama pin maps"
value={data?.namePin}
onChangeText={(value) => setData({ ...data, namePin: value })}
/> />
<Spacing /> <Spacing />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." /> <InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded
image={
image
? image?.uri
: API_IMAGE.GET({ fileId: data?.imageId as string })
}
/>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => { onPress={() => {
console.log("Upload foto "); pickFile({
router.navigate(`/take-picture/${id}`); allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
> >
Upload Upload
@@ -57,3 +229,16 @@ export default function MapsEdit() {
</ViewWrapper> </ViewWrapper>
); );
} }
const styles = StyleSheet.create({
container: {
width: "100%",
backgroundColor: "#f5f5f5",
overflow: "hidden",
borderRadius: 8,
marginBottom: 20,
},
map: {
flex: 1,
},
});

View File

@@ -6,21 +6,96 @@ import {
InformationBox, InformationBox,
LandscapeFrameUploaded, LandscapeFrameUploaded,
Spacing, Spacing,
TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import MapSelected from "@/components/Map/MapSelected";
import DIRECTORY_ID from "@/constants/directory-id";
import { useAuth } from "@/hooks/use-auth";
import { apiMapsCreate } from "@/service/api-client/api-maps";
import { uploadFileService } from "@/service/upload-service";
import pickFile, { IFileData } from "@/utils/pickFile";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { LatLng } from "react-native-maps";
import Toast from "react-native-toast-message";
export default function MapsCreate() { export default function MapsCreate() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [selectedLocation, setSelectedLocation] = useState<LatLng | null>(null);
const [name, setName] = useState<string>("");
const [image, setImage] = useState<IFileData | null>(null);
const [isLoading, setLoading] = useState(false);
const handleSubmit = async () => {
try {
setLoading(true);
let newData: any;
newData = {
authorId: user?.id,
portofolioId: id,
namePin: name,
latitude: selectedLocation?.latitude,
longitude: selectedLocation?.longitude,
};
if (image) {
const responseUpload = await uploadFileService({
dirId: DIRECTORY_ID.map_image,
imageUri: image?.uri,
});
if (!responseUpload?.data?.id) {
Toast.show({
type: "error",
text1: "Gagal mengunggah gambar",
});
return;
}
const imageId = responseUpload?.data?.id;
newData = {
authorId: user?.id,
portofolioId: id,
namePin: name,
latitude: selectedLocation?.latitude,
longitude: selectedLocation?.longitude,
imageId: imageId,
};
}
const response = await apiMapsCreate({
data: newData,
});
if (!response.success) {
Toast.show({
type: "error",
text1: "Gagal menambahkan map",
});
return;
}
Toast.show({
type: "success",
text1: "Map berhasil ditambahkan",
});
router.back();
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => { isLoading={isLoading}
console.log(`Simpan maps ${id}`); disabled={!selectedLocation || name === ""}
router.replace(`/portofolio/${id}`); onPress={handleSubmit}
}}
> >
Simpan Simpan
</ButtonCustom> </ButtonCustom>
@@ -30,25 +105,34 @@ export default function MapsCreate() {
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter}>
<InformationBox text="Tentukan lokasi pin map dengan menekan pada map." /> <InformationBox text="Tentukan lokasi pin map dengan menekan pada map." />
<BaseBox style={{ height: 400 }}> <BaseBox>
<TextCustom>Maps Her</TextCustom> <MapSelected
selectedLocation={selectedLocation as any}
setSelectedLocation={setSelectedLocation}
/>
</BaseBox> </BaseBox>
<TextInputCustom <TextInputCustom
required required
label="Nama Pin" label="Nama Pin"
placeholder="Masukkan nama pin maps" placeholder="Masukkan nama pin maps"
value={name}
onChangeText={setName}
/> />
<Spacing height={50} /> <Spacing height={50} />
<InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." /> <InformationBox text="Upload foto lokasi bisnis anda untuk ditampilkan dalam detail maps." />
<LandscapeFrameUploaded /> <LandscapeFrameUploaded image={image?.uri} />
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => { onPress={() => {
console.log("Upload foto "); pickFile({
router.navigate(`/take-picture/${id}`); allowedType: "image",
setImageUri(file) {
setImage(file);
},
});
}} }}
> >
Upload Upload

View File

@@ -1,9 +1,234 @@
import { MapCustom, ViewWrapper } from "@/components"; import {
ButtonCustom,
DrawerCustom,
DummyLandscapeImage,
Grid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import API_IMAGE from "@/constants/api-storage";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { apiMapsGetAll } from "@/service/api-client/api-maps";
import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { Image } from "expo-image";
import { router, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { View } from "react-native";
import MapView, { Marker } from "react-native-maps";
const defaultRegion = {
latitude: -8.737109,
longitude: 115.1756897,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
height: 300,
};
export interface LocationItem {
id: string | number;
latitude: number;
longitude: number;
name: string;
imageId?: string;
}
export default function Maps() { export default function Maps() {
const [list, setList] = useState<any[] | 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) {
setList(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadList(false);
}
};
return ( return (
<>
<ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}> <ViewWrapper style={{ paddingInline: 0, paddingBlock: 0 }}>
<MapCustom height={"100%"} /> {/* <MapCustom height={"100%"} /> */}
<View style={{ flex: 1 }}>
{loadList ? (
<MapView
initialRegion={defaultRegion}
style={{
width: "100%",
height: "100%",
}}
/>
) : (
<MapView
initialRegion={defaultRegion}
style={{
width: "100%",
height: "100%",
}}
>
{list?.map((item: any, index: number) => {
return (
<Marker
key={item?.id}
coordinate={{
latitude: item?.latitude,
longitude: item?.longitude,
}}
title={item?.namePin}
onPress={() => {
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,
});
}}
// Gunakan gambar kustom jika tersedia
>
<View style={{}}>
<Image
source={{
uri: API_IMAGE.GET({
fileId: item?.Portofolio?.logoId,
}),
}}
style={{
width: 30,
height: 30,
borderRadius: 100,
borderWidth: 1,
}}
/>
</View>
</Marker>
);
})}
</MapView>
)}
</View>
</ViewWrapper> </ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<DummyLandscapeImage height={200} imageId={selected.imageId} />
<Spacing />
<StackCustom gap={"xs"}>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<FontAwesome
name="building-o"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.namePin}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="list-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.bidangBisnis}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="call-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{selected.nomorTelepon}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="location-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<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>
</>
); );
} }

View File

@@ -1,10 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { import {
BoxButtonOnFooter, ActionIcon,
AvatarComp,
BaseBox,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, CenterCustom,
Grid, Grid,
InformationBox, InformationBox,
LandscapeFrameUploaded,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
@@ -13,49 +15,122 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import dummyMasterBidangBisnis from "@/lib/dummy-data/master-bidang-bisnis"; import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import dummyMasterSubBidangBisnis from "@/lib/dummy-data/master-sub-bidang-bisnis"; import DUMMY_IMAGE from "@/constants/dummy-image-value";
import Portofolio_ButtonCreate from "@/screens/Portofolio/ButtonCreatePortofolio";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import pickImage from "@/utils/pickImage";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { Image } from "expo-image";
import { useState } from "react"; import { useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native"; import { Text, TouchableOpacity, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number"; import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper";
export default function PortofolioCreate() { export default function PortofolioCreate() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>(""); const [inputValue, setInputValue] = useState<string>("");
const [data, setData] = useState({ const [data, setData] = useState({
name: "", namaBisnis: "",
bidang_usaha: "", masterBidangBisnisId: "",
sub_bidang_usaha: "", alamatKantor: "",
alamat: "", tlpn: "",
nomor_telepon: "",
deskripsi: "", deskripsi: "",
}); });
const [imageUri, setImageUri] = useState<string | null>(null);
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
const [subBidangBisnis, setSubBidangBisnis] = useState<
IMasterSubBidangBisnis[]
>([]);
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState([
{
id: "",
},
]);
const [dataMedsos, setDataMedsos] = useState({
facebook: "",
twitter: "",
instagram: "",
youtube: "",
tiktok: "",
});
const [isLoadingCreate, setIsLoadingCreate] = useState(false);
function handleInputValue(phoneNumber: string) { function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber); setInputValue(phoneNumber);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
const fixNumber = inputValue.replace(/\s+/g, "");
const realNumber = callingCode + fixNumber;
setData({ ...data, tlpn: realNumber });
} }
function handleSelectedCountry(country: ICountry) { function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country); setSelectedCountry(country);
} }
function handleSave() { useEffect(() => {
console.log("Selanjutnya"); onLoadMaster();
router.replace(`/maps/create`); onLoadMasterSubBidangBisnis();
} }, []);
const buttonSave = ( const onLoadMaster = async () => {
<BoxButtonOnFooter> try {
<ButtonCustom onPress={handleSave}>Selanjutnya</ButtonCustom> const response = await apiMasterBidangBisnis();
</BoxButtonOnFooter> setBidangBisnis(response.data);
} catch (error) {
setBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const onLoadMasterSubBidangBisnis = async () => {
try {
const response = await apiMasterSubBidangBisnis({});
setSubBidangBisnis(response.data);
} catch (error) {
setSubBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id
); );
setSelectedSubBidang(selectedList as any[]);
};
return ( return (
<ViewWrapper footerComponent={buttonSave}> <ViewWrapper
footerComponent={
<Portofolio_ButtonCreate
id={id as string}
data={data}
dataMedsos={dataMedsos}
imageUri={imageUri}
subBidangSelected={listSubBidangSelected}
isLoadingCreate={isLoadingCreate}
setIsLoadingCreate={setIsLoadingCreate}
/>
}
>
{/* <TextCustom>Portofolio Create {id}</TextCustom> */} {/* <TextCustom>Portofolio Create {id}</TextCustom> */}
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<InformationBox text="Lengkapi data bisnis anda." /> <InformationBox text="Lengkapi data bisnis anda." />
@@ -63,51 +138,121 @@ export default function PortofolioCreate() {
required required
label="Nama Bisnis" label="Nama Bisnis"
placeholder="Masukkan nama bisnis" placeholder="Masukkan nama bisnis"
onChangeText={(value: any) => setData({ ...data, namaBisnis: value })}
/> />
<SelectCustom <SelectCustom
label="Bidang Usaha" label="Bidang Usaha"
required required
data={dummyMasterBidangBisnis.map((item) => ({ data={bidangBisnis.map((item) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))}
value={data.bidang_usaha} value={data.masterBidangBisnisId}
onChange={(value) => { onChange={(value) => {
setData({ ...(data as any), bidang_usaha: value }); const isSameBidang = data.masterBidangBisnisId === value;
if (!isSameBidang) {
setListSubBidangSelected([{ id: "" }]);
}
setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}} }}
/> />
<Grid> {listSubBidangSelected.map((item, index) => (
<Grid.Col span={10}>
<SelectCustom <SelectCustom
// disabled key={index}
disabled={data.masterBidangBisnisId === ""}
label="Sub Bidang Usaha" label="Sub Bidang Usaha"
required required
data={dummyMasterSubBidangBisnis.map((item) => ({ data={_.map(selectedSubBidang as any)
.filter((option: any) => {
const selectedValues = listSubBidangSelected.map((s) => s.id);
return (
option.id === item.id || // biarkan tetap muncul kalau ini valuenya sendiri
!selectedValues.includes(option.id)
);
})
.map((e: any) => ({
value: e.id,
label: e.name,
}))}
value={item.id || null}
onChange={(value) => {
const list = _.clone(listSubBidangSelected);
list[index].id = value as any;
setListSubBidangSelected(list);
}}
/>
))}
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,
{ id: "" },
]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
const list = _.clone(listSubBidangSelected);
list.pop();
setListSubBidangSelected(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
{/* <SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis.map((item) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))}
value={data.sub_bidang_usaha} value={null}
onChange={(value) => { onChange={(value) => {
setData({ ...(data as any), sub_bidang_usaha: value }); setData({ ...(data as any), masterBidangBisnisId: value });
handlerSelectedSubBidang({ id: value as string });
}} }}
/> /> */}
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}> {/* <ButtonCenteredOnly
onPress={() => {
setListSubBidangSelected([...listSubBidangSelected, { id: "" }]);
}}
>
Tambah Pilihan Tambah Pilihan
</ButtonCenteredOnly> </ButtonCenteredOnly>
<Spacing /> <Spacing /> */}
{/* <TextCustom>{JSON.stringify(bidangBisnis, null, 2)}</TextCustom> */}
<View> <View>
<View style={{ flexDirection: "row", alignItems: "center" }}> <View style={{ flexDirection: "row", alignItems: "center" }}>
@@ -132,6 +277,9 @@ export default function PortofolioCreate() {
required required
label="Alamat Bisnis" label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis" placeholder="Masukkan alamat bisnis"
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/> />
<TextAreaCustom <TextAreaCustom
@@ -144,18 +292,26 @@ export default function PortofolioCreate() {
maxRows={5} maxRows={5}
required required
showCount showCount
maxLength={100} maxLength={1000}
/> />
<Spacing /> <Spacing />
{/* Logo */} {/* Logo */}
<InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." /> <InformationBox text="Upload logo bisnis anda untuk di tampilaka pada portofolio." />
<LandscapeFrameUploaded />
<CenterCustom>
<Avatar.Image
source={imageUri ? { uri: imageUri } : DUMMY_IMAGE.dummy_image}
size={200}
/>
</CenterCustom>
<Spacing />
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => { onPress={() => {
console.log("Upload logo >>", id); pickImage({
router.navigate(`/(application)/(image)/take-picture/${id}`); setImageUri,
});
}} }}
> >
Upload Upload
@@ -167,22 +323,37 @@ export default function PortofolioCreate() {
<TextInputCustom <TextInputCustom
label="Tiktok" label="Tiktok"
placeholder="Masukkan username tiktok" placeholder="Masukkan username tiktok"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, tiktok: value })
}
/> />
<TextInputCustom <TextInputCustom
label="Facebook" label="Facebook"
placeholder="Masukkan username facebook" placeholder="Masukkan username facebook"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, facebook: value })
}
/> />
<TextInputCustom <TextInputCustom
label="Instagram" label="Instagram"
placeholder="Masukkan username instagram" placeholder="Masukkan username instagram"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, instagram: value })
}
/> />
<TextInputCustom <TextInputCustom
label="Twitter" label="Twitter"
placeholder="Masukkan username twitter" placeholder="Masukkan username twitter"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, twitter: value })
}
/> />
<TextInputCustom <TextInputCustom
label="Youtube" label="Youtube"
placeholder="Masukkan username youtube" placeholder="Masukkan username youtube"
onChangeText={(value: any) =>
setDataMedsos({ ...dataMedsos, youtube: value })
}
/> />
<Spacing /> <Spacing />
</StackCustom> </StackCustom>

View File

@@ -1,25 +1,125 @@
import { import {
AvatarCustom,
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import API_STRORAGE from "@/constants/base-url-api-strorage";
import DIRECTORY_ID from "@/constants/directory-id";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { useAuth } from "@/hooks/use-auth";
import { apiFileDelete } from "@/service/api-client/api-file";
import {
apiGetOnePortofolio,
apiUpdatePortofolio,
} from "@/service/api-client/api-portofolio";
import { uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function PortofolioEditLogo() { export default function PortofolioEditLogo() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [logoId, setLogoId] = useState<any>();
const [imageUri, setImageUri] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const { token } = useAuth();
useEffect(() => {
onLoadData(id as string);
}, [id]);
const onLoadData = async (id: string) => {
const response = await apiGetOnePortofolio({ id: id });
console.log(
"Response portofolio >>",
JSON.stringify(response.data.logoId, null, 2)
);
setLogoId(response.data.logoId);
};
async function onUpload() {
try {
setIsLoading(true);
const response = await uploadFileService({
imageUri,
dirId: DIRECTORY_ID.portofolio_logo,
});
if (response.success) {
const fileId = response.data.id;
const responseUpdate = await apiUpdatePortofolio({
id: id as string,
data: { fileId },
category: "logo",
});
if (!responseUpdate.success) {
Toast.show({
type: "error",
text1: "Info",
text2: responseUpdate.message,
});
return;
}
if (logoId) {
const deletePrevFile = await apiFileDelete({
token: token as string,
id: logoId as string,
});
if (!deletePrevFile.success) {
console.log("error delete prev file >>", deletePrevFile.message);
}
}
Toast.show({
type: "success",
text1: "Sukses",
text2: "Logo berhasil diupdate",
});
router.back();
}
} catch (error) {
Toast.show({
type: "error",
text1: "Gagal",
text2: error as string,
});
} finally {
setIsLoading(false);
}
}
const image = imageUri ? (
<Image source={{ uri: imageUri }} style={{ width: 200, height: 200 }} />
) : (
<Image
source={
logoId
? { uri: API_STRORAGE.GET({ fileId: logoId }) }
: DUMMY_IMAGE.avatar
}
style={{ width: 200, height: 200 }}
/>
);
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
isLoading={isLoading}
disabled={isLoading}
onPress={() => { onPress={() => {
console.log("Simpan logo "); onUpload();
router.back();
}} }}
> >
Simpan Update
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -34,13 +134,17 @@ export default function PortofolioEditLogo() {
height: 250, height: 250,
}} }}
> >
<AvatarCustom size="xl" /> {image}
</BaseBox> </BaseBox>
<ButtonCenteredOnly <ButtonCenteredOnly
icon="upload" icon="upload"
onPress={() => router.navigate(`/take-picture/${id}`)} onPress={() => {
pickImage({
setImageUri,
});
}}
> >
Update Upload
</ButtonCenteredOnly> </ButtonCenteredOnly>
</ViewWrapper> </ViewWrapper>
</> </>

View File

@@ -4,20 +4,87 @@ import {
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import {
apiGetOnePortofolio,
apiUpdatePortofolio,
} from "@/service/api-client/api-portofolio";
import { useLocalSearchParams, router } from "expo-router"; import { useLocalSearchParams, router } from "expo-router";
import { useEffect, useState } from "react";
import Toast from "react-native-toast-message";
export default function PortofolioEditSocialMedia() { export default function PortofolioEditSocialMedia() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
console.log("ID >>", id);
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<any>({
facebook: "",
twitter: "",
instagram: "",
tiktok: "",
youtube: "",
});
useEffect(() => {
onLoadData(id as string);
}, [id]);
const onLoadData = async (id: string) => {
const response = await apiGetOnePortofolio({ id: id });
console.log(
"Response portofolio >>",
JSON.stringify(response.data.Portofolio_MediaSosial, null, 2)
);
const data = response.data.Portofolio_MediaSosial;
setData({
facebook: data.facebook,
twitter: data.twitter,
instagram: data.instagram,
tiktok: data.tiktok,
youtube: data.youtube,
});
};
const onSubmitUpdate = async () => {
try {
setIsLoading(true);
const response = await apiUpdatePortofolio({
id: id as string,
data: data,
category: "medsos",
});
if (!response.success) {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
return;
}
Toast.show({
type: "success",
text1: "Sukses",
text2: "Data media terupdate",
});
router.back();
} catch (error) {
console.log("Error onSubmitUpdate", error);
} finally {
setIsLoading(false);
}
};
const buttonFooter = ( const buttonFooter = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom <ButtonCustom
onPress={() => { isLoading={isLoading}
console.log(`Simpan sosmed ${id}`); disabled={isLoading}
router.back(); onPress={onSubmitUpdate}
}}
> >
Simpan Update
</ButtonCustom> </ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
@@ -25,11 +92,36 @@ export default function PortofolioEditSocialMedia() {
return ( return (
<> <>
<ViewWrapper footerComponent={buttonFooter}> <ViewWrapper footerComponent={buttonFooter}>
<TextInputCustom label="Tiktok" placeholder="Masukkan tiktok" /> <TextInputCustom
<TextInputCustom label="Instagram" placeholder="Masukkan instagram" /> value={data.tiktok}
<TextInputCustom label="Facebook" placeholder="Masukkan facebook" /> onChangeText={(value) => setData({ ...data, tiktok: value })}
<TextInputCustom label="Twitter" placeholder="Masukkan twitter" /> label="Tiktok"
<TextInputCustom label="Youtube" placeholder="Masukkan youtube" /> placeholder="Masukkan tiktok"
/>
<TextInputCustom
value={data.instagram}
onChangeText={(value) => setData({ ...data, instagram: value })}
label="Instagram"
placeholder="Masukkan instagram"
/>
<TextInputCustom
value={data.facebook}
onChangeText={(value) => setData({ ...data, facebook: value })}
label="Facebook"
placeholder="Masukkan facebook"
/>
<TextInputCustom
value={data.twitter}
onChangeText={(value) => setData({ ...data, twitter: value })}
label="Twitter"
placeholder="Masukkan twitter"
/>
<TextInputCustom
value={data.youtube}
onChangeText={(value) => setData({ ...data, youtube: value })}
label="Youtube"
placeholder="Masukkan youtube"
/>
</ViewWrapper> </ViewWrapper>
</> </>
); );

View File

@@ -0,0 +1,357 @@
import {
ActionIcon,
BoxButtonOnFooter,
ButtonCustom,
CenterCustom,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
apiGetOnePortofolio,
apiUpdatePortofolio,
} from "@/service/api-client/api-portofolio";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useEffect, useState } from "react";
import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import Toast from "react-native-toast-message";
interface IFormData {
id_Portofolio: string;
namaBisnis: string;
alamatKantor: string;
tlpn: string;
deskripsi: string;
masterBidangBisnisId: string;
subBidang: any[];
}
interface IListSubBidangSelected {
id: string;
MasterSubBidangBisnis: {
id: string;
name: string;
masterBidangBisnisId: string;
};
}
export default function PortofolioEdit() {
const { id } = useLocalSearchParams();
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<any>({});
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [bidangBisnis, setBidangBisnis] = useState<IMasterBidangBisnis[]>([]);
const [subBidangBisnis, setSubBidangBisnis] = useState<
IMasterSubBidangBisnis[]
>([]);
const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState<
IListSubBidangSelected[]
>([
{
id: "",
MasterSubBidangBisnis: {
id: "",
name: "",
masterBidangBisnisId: "",
},
},
]);
function handleInputValue(phoneNumber: string) {
setData({ ...data, tlpn: phoneNumber });
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
useEffect(() => {
onLoadData(id as string);
onLoadMasterBidang();
onLoadMasterSubBidangBisnis();
}, [id]);
const onLoadData = async (id: string) => {
const response = await apiGetOnePortofolio({ id: id });
if (response.data.tlpn && response.data.tlpn.includes("62")) {
const fixNumber = response.data.tlpn.replace("62", "");
setData({ ...response.data, tlpn: fixNumber });
}
};
const onLoadMasterBidang = async () => {
try {
const response = await apiMasterBidangBisnis();
setBidangBisnis(response.data);
} catch (error) {
setBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
}
};
async function onLoadMasterSubBidangBisnis() {
try {
const response = await apiMasterSubBidangBisnis({});
if (response.success) {
setSubBidangBisnis(response.data);
}
} catch (error) {
console.error("Error on load master sub bidang bisnis", error);
}
}
const handleSubmitUpdate = async () => {
try {
setIsLoading(true);
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
const fixNumber = data.tlpn.replace(/\s+/g, "");
const realNumber = callingCode + fixNumber;
const newData: IFormData = {
id_Portofolio: data.id_Portofolio,
namaBisnis: data.namaBisnis,
alamatKantor: data.alamatKantor,
tlpn: realNumber,
deskripsi: data.deskripsi,
masterBidangBisnisId: data.masterBidangBisnisId,
subBidang: listSubBidangSelected,
};
const response = await apiUpdatePortofolio({
id: id as string,
data: newData,
category: "detail",
});
if (!response.success) {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
return;
}
Toast.show({
type: "success",
text1: "Sukses",
text2: "Data terupdate",
});
router.back();
} catch (error) {
console.log("Error handleSubmitUpdate", error);
} finally {
setIsLoading(false);
}
};
const buttonUpdate = (
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
disabled={isLoading}
onPress={handleSubmitUpdate}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonUpdate}>
<StackCustom gap={"xs"}>
<TextInputCustom
required
label="Nama Bisnis"
placeholder="Masukkan nama bisnis"
value={data.namaBisnis}
onChangeText={(value: any) =>
setData({ ...data, namaBisnis: value })
}
/>
<SelectCustom
label="Bidang Usaha"
required
data={bidangBisnis?.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.masterBidangBisnisId}
onChange={(value) => {
setData({ ...(data as any), masterBidangBisnisId: value });
}}
/>
{listSubBidangSelected.map((item, index) => (
<SelectCustom
key={index}
label="Sub Bidang Usaha"
required
data={subBidangBisnis?.map((item) => ({
label: item.name,
value: item.id,
}))}
value={item.id || null}
onChange={(value) => {
console.log("Value >>", value);
}}
/>
))}
<CenterCustom>
<View
style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
>
<ActionIcon
// disabled={
// selectedSubBidang.length === listSubBidangSelected.length
// }
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,
{
id: "",
MasterSubBidangBisnis: {
id: "",
name: "",
masterBidangBisnisId: "",
},
},
]);
}}
icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
// disabled={listSubBidangSelected.length <= 1}
onPress={() => {
const list = _.clone(listSubBidangSelected);
list.pop();
setListSubBidangSelected(list);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing />
{/* <Grid>
<Grid.Col span={10}>
<SelectCustom
// disabled
label="Sub Bidang Usaha"
required
data={dummyMasterSubBidangBisnis.map((item) => ({
label: item.name,
value: item.id,
}))}
value={data.masterSubBidangBisnisId}
onChange={(value) => {
setData({ ...(data as any), masterSubBidangBisnisId: value });
}}
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid> */}
{/* <ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing /> */}
<View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}>
Nomor Telepon
</TextCustom>
<Text style={{ color: "red" }}> *</Text>
</View>
<Spacing height={5} />
<PhoneInput
value={data.tlpn}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="xxx-xxx-xxx"
/>
</View>
<Spacing />
<TextInputCustom
required
label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis"
value={data.alamatKantor}
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/>
<TextAreaCustom
label="Deskripsi Bisnis"
placeholder="Masukkan deskripsi bisnis"
value={data.deskripsi}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
autosize
minRows={2}
maxRows={5}
required
showCount
maxLength={1000}
/>
<Spacing />
<TextCustom>{JSON.stringify(subBidangBisnis, null, 2)}</TextCustom>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -1,8 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { import {
ActionIcon,
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
Grid, CenterCustom,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
@@ -12,46 +13,319 @@ import {
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import dummyMasterBidangBisnis from "@/lib/dummy-data/master-bidang-bisnis"; import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import dummyMasterSubBidangBisnis from "@/lib/dummy-data/master-sub-bidang-bisnis"; import {
apiMasterBidangBisnis,
apiMasterSubBidangBisnis,
} from "@/service/api-client/api-master";
import {
apiGetOnePortofolio,
apiUpdatePortofolio,
} from "@/service/api-client/api-portofolio";
import {
IMasterBidangBisnis,
IMasterSubBidangBisnis,
} from "@/types/Type-Master";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import _ from "lodash";
import { Text, TouchableOpacity, View } from "react-native"; import { useEffect, useState } from "react";
import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number"; import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { ActivityIndicator } from "react-native-paper";
import Toast from "react-native-toast-message";
interface IFormData {
id_Portofolio: string;
namaBisnis: string;
alamatKantor: string;
tlpn: string;
deskripsi: string;
masterBidangBisnisId: string;
subBidang: any[];
}
interface IListSubBidangSelected {
id: string;
MasterSubBidangBisnis?: {
id?: string;
name?: string;
masterBidangBisnisId?: string;
};
}
export default function PortofolioEdit() { export default function PortofolioEdit() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null); const [isLoading, setIsLoading] = useState(false);
const [inputValue, setInputValue] = useState<string>(""); const [data, setData] = useState<any>({});
const [data, setData] = useState({ const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
name: "", const [bidangBisnis, setBidangBisnis] = useState<
bidang_usaha: "", IMasterBidangBisnis[] | null
sub_bidang_usaha: "", >(null);
alamat: "", const [subBidangBisnis, setSubBidangBisnis] = useState<
nomor_telepon: "", IMasterSubBidangBisnis[] | null
deskripsi: "", >(null);
}); const [selectedSubBidang, setSelectedSubBidang] = useState<string[]>([]);
const [listSubBidangSelected, setListSubBidangSelected] = useState<
IListSubBidangSelected[]
>([]);
function handleInputValue(phoneNumber: string) { function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber); setData({ ...data, tlpn: phoneNumber });
} }
function handleSelectedCountry(country: ICountry) { function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country); setSelectedCountry(country);
} }
function handleSave() { const onLoadMasterBidang = async () => {
console.log(`Update portofolio berhasil ${id}`); try {
router.back(); const response = await apiMasterBidangBisnis();
setBidangBisnis(response.data);
return response.success;
} catch (error) {
setBidangBisnis([]);
console.log("Error onLoadMasterBidangBisnis", error);
} }
};
async function onLoadMasterSubBidangBisnis() {
try {
const response = await apiMasterSubBidangBisnis({});
setSubBidangBisnis(response.data);
return response.success;
} catch (error) {
console.error("Error on load master sub bidang bisnis", error);
}
}
const handleLoadMaster = async (id: string) => {
const loadBidang = await onLoadMasterBidang();
const loadSubBidang = await onLoadMasterSubBidangBisnis();
if (!loadBidang || !loadSubBidang) {
return;
}
onLoadData(id);
};
useEffect(() => {
handleLoadMaster(id as any);
}, [id]);
const onLoadData = async (id: string) => {
const response = await apiGetOnePortofolio({ id: id });
if (response.success) {
const fixNumber = response.data.tlpn.replace("62", "");
setData({ ...response.data, tlpn: fixNumber });
// Cek apakah ada sub bidang bisnis yang terpilih
const prevSubBidang = response.data.Portofolio_BidangDanSubBidangBisnis;
if (prevSubBidang && prevSubBidang.length > 0) {
setListSubBidangSelected(prevSubBidang);
} else {
// Jika tidak ada sub bidang yang terpilih sebelumnya, tetap inisialisasi dengan array kosong
setListSubBidangSelected([
{
id: "",
MasterSubBidangBisnis: {
id: "",
name: "",
},
},
]);
}
const bisnisId = response.data.masterBidangBisnisId;
handleLoadSelectedSubBidang({
id: bisnisId,
});
}
};
// Handler untuk saat komponen pertama kali load
const handleLoadSelectedSubBidang = ({ id }: { id: string }) => {
if (!subBidangBisnis) return;
const filteredSubBidang: any = subBidangBisnis.filter((item) => {
return item.masterBidangBisnisId === id;
});
setSelectedSubBidang(filteredSubBidang);
};
// Handler untuk menambah sub bidang bisnis
const handleAddSubBidang = () => {
setListSubBidangSelected([
...listSubBidangSelected,
{
id: "",
MasterSubBidangBisnis: { id: "", name: "" },
},
]);
};
// Handler untuk menghapus sub bidang bisnis
const handleRemoveSubBidang = (index: number) => {
if (listSubBidangSelected.length <= 1) return;
const updatedList = [...listSubBidangSelected];
updatedList.splice(index, 1);
setListSubBidangSelected(updatedList);
};
// Handler untuk perubahan bidang bisnis
const handleBidangBisnisChange = (val: string) => {
const isSameBidang = data?.MasterBidangBisnis?.id === val;
setData({ ...(data as any), masterBidangBisnisId: val });
// Reset sub bidang jika ganti bidang
if (!isSameBidang) {
setListSubBidangSelected([
{
id: "",
MasterSubBidangBisnis: { id: "", name: "" },
},
]);
}
handleLoadSelectedSubBidang({ id: val });
};
// Handler untuk update sub bidang
const handleSubBidangChange = (value: string, index: number) => {
const select = selectedSubBidang.find((sub: any) => sub.id === value);
const list: any = _.cloneDeep(listSubBidangSelected);
list[index] = {
id: "",
MasterSubBidangBisnis: select || {
id: value,
name: "",
masterBidangBisnisId: "",
},
};
setListSubBidangSelected(list);
};
useEffect(() => {
if (subBidangBisnis?.length !== undefined && data.masterBidangBisnisId) {
handleLoadSelectedSubBidang({
id: data.masterBidangBisnisId,
});
}
}, [subBidangBisnis, data.masterBidangBisnisId]);
function validateData(data: any) {
if (
!data.namaBisnis ||
!data.alamatKantor ||
!data.tlpn ||
!data.deskripsi ||
!data.masterBidangBisnisId
) {
return false;
}
return true;
}
function validateDataSubBidang(dataArray: any[]) {
return !dataArray.some(
(item: any) =>
!item.MasterSubBidangBisnis.id ||
item.MasterSubBidangBisnis.id.trim() === ""
);
}
const handleSubmitUpdate = async () => {
const callingCode = selectedCountry?.callingCode.replace(/^\+/, "") || "";
const fixNumber = data.tlpn.replace(/\s+/g, "");
const realNumber = callingCode + fixNumber;
const newData: IFormData = {
id_Portofolio: data.id_Portofolio,
namaBisnis: data.namaBisnis,
alamatKantor: data.alamatKantor,
tlpn: realNumber,
deskripsi: data.deskripsi,
masterBidangBisnisId: data.masterBidangBisnisId,
subBidang: listSubBidangSelected,
};
if (!validateData(newData)) {
return Toast.show({
type: "error",
text1: "Harap lengkapi data",
});
}
if (!validateDataSubBidang(listSubBidangSelected as any)) {
return Toast.show({
type: "error",
text1: "Harap lengkapi sub bidang",
});
}
try {
setIsLoading(true);
const response = await apiUpdatePortofolio({
id: id as string,
data: newData,
category: "detail",
});
if (!response.success) {
Toast.show({
type: "info",
text1: "Info",
text2: response.message,
});
return;
}
Toast.show({
type: "success",
text1: "Sukses",
text2: "Data terupdate",
});
router.back();
} catch (error) {
console.log("Error handleSubmitUpdate", error);
} finally {
setIsLoading(false);
}
};
const buttonUpdate = ( const buttonUpdate = (
<BoxButtonOnFooter> <BoxButtonOnFooter>
<ButtonCustom onPress={handleSave}>Simpan</ButtonCustom> <ButtonCustom
isLoading={isLoading}
disabled={isLoading}
onPress={handleSubmitUpdate}
>
Update
</ButtonCustom>
</BoxButtonOnFooter> </BoxButtonOnFooter>
); );
if (!bidangBisnis || !subBidangBisnis) {
return (
<>
<ViewWrapper>
<ActivityIndicator size="large" color={MainColor.yellow} />
</ViewWrapper>
</>
);
}
return ( return (
<> <>
<ViewWrapper footerComponent={buttonUpdate}> <ViewWrapper footerComponent={buttonUpdate}>
@@ -60,50 +334,98 @@ export default function PortofolioEdit() {
required required
label="Nama Bisnis" label="Nama Bisnis"
placeholder="Masukkan nama bisnis" placeholder="Masukkan nama bisnis"
value={data.namaBisnis}
onChangeText={(value: any) =>
setData({ ...data, namaBisnis: value })
}
/> />
<SelectCustom <SelectCustom
label="Bidang Usaha" label="Bidang Usaha"
required required
data={dummyMasterBidangBisnis.map((item) => ({ data={bidangBisnis?.map((item) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))}
value={data.bidang_usaha} value={data.masterBidangBisnisId}
onChange={(value) => { onChange={(value: any) => {
setData({ ...(data as any), bidang_usaha: value }); handleBidangBisnisChange(value);
}} }}
/> />
<Grid> {listSubBidangSelected.map((item, index) => {
<Grid.Col span={10}> // Filter data untuk select sub bidang, menghilangkan yang sudah dipilih kecuali untuk item ini sendiri
const selectedIds = listSubBidangSelected
.filter((_, i) => i !== index)
.map((s) => s.MasterSubBidangBisnis?.id)
.filter((id) => id); // Filter hanya yang memiliki id (tidak kosong)
const availableSubBidangOptions = (selectedSubBidang || [])
.filter((sub: any) => {
// Tampilkan jika ini adalah opsi yang dipilih saat ini atau belum dipilih di sub bidang lainnya
return (
sub.id === item.MasterSubBidangBisnis?.id ||
!selectedIds.includes(sub.id)
);
})
.map((sub: any) => ({
value: sub.id,
label: sub.name,
}));
return (
<SelectCustom <SelectCustom
// disabled key={index}
label="Sub Bidang Usaha" label="Sub Bidang Usaha"
required required
data={dummyMasterSubBidangBisnis.map((item) => ({ data={availableSubBidangOptions}
label: item.name, value={item.MasterSubBidangBisnis?.id || null}
value: item.id, onChange={(value: any) => {
}))} handleSubBidangChange(value, index);
value={data.sub_bidang_usaha}
onChange={(value) => {
setData({ ...(data as any), sub_bidang_usaha: value });
}} }}
/> />
</Grid.Col> );
<Grid.Col })}
span={2}
style={{ alignItems: "center", justifyContent: "center" }} <CenterCustom>
<View
style={{ flexDirection: "row", alignItems: "center", gap: 10 }}
> >
<TouchableOpacity onPress={() => console.log("delete")}> <ActionIcon
<Ionicons name="trash" size={24} color={MainColor.red} /> disabled={
</TouchableOpacity> selectedSubBidang.length === listSubBidangSelected.length
</Grid.Col> }
</Grid> onPress={() => {
<ButtonCenteredOnly onPress={() => console.log("add")}> handleAddSubBidang();
Tambah Pilihan }}
</ButtonCenteredOnly> icon={
<Ionicons
name="add-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
<ActionIcon
disabled={listSubBidangSelected.length <= 1}
onPress={() => {
handleRemoveSubBidang(listSubBidangSelected.length - 1);
}}
icon={
<Ionicons
name="remove-circle-outline"
size={ICON_SIZE_XLARGE}
color={MainColor.black}
/>
}
size="xl"
/>
</View>
</CenterCustom>
<Spacing /> <Spacing />
<View> <View>
<View style={{ flexDirection: "row", alignItems: "center" }}> <View style={{ flexDirection: "row", alignItems: "center" }}>
<TextCustom semiBold style={{ color: MainColor.white_gray }}> <TextCustom semiBold style={{ color: MainColor.white_gray }}>
@@ -113,7 +435,7 @@ export default function PortofolioEdit() {
</View> </View>
<Spacing height={5} /> <Spacing height={5} />
<PhoneInput <PhoneInput
value={inputValue} value={data.tlpn}
onChangePhoneNumber={handleInputValue} onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry} selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry} onChangeSelectedCountry={handleSelectedCountry}
@@ -127,6 +449,10 @@ export default function PortofolioEdit() {
required required
label="Alamat Bisnis" label="Alamat Bisnis"
placeholder="Masukkan alamat bisnis" placeholder="Masukkan alamat bisnis"
value={data.alamatKantor}
onChangeText={(value: any) =>
setData({ ...data, alamatKantor: value })
}
/> />
<TextAreaCustom <TextAreaCustom
@@ -141,7 +467,7 @@ export default function PortofolioEdit() {
maxRows={5} maxRows={5}
required required
showCount showCount
maxLength={100} maxLength={1000}
/> />
<Spacing /> <Spacing />
</StackCustom> </StackCustom>

View File

@@ -1,20 +1,42 @@
import { AlertCustom, DrawerCustom } from "@/components"; /* eslint-disable react-hooks/exhaustive-deps */
import {
ButtonCustom,
DrawerCustom,
DummyLandscapeImage,
LoaderCustom,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton"; import LeftButtonCustom from "@/components/Button/BackButton";
import GridTwoView from "@/components/_ShareComponent/GridTwoView";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import Portofolio_BusinessLocation from "@/screens/Portofolio/BusinessLocationSection";
import Portofolio_ButtonDelete from "@/screens/Portofolio/ButtonDelete";
import Portofolio_Data from "@/screens/Portofolio/DataPortofolio";
import { drawerItemsPortofolio } from "@/screens/Portofolio/ListPage"; import { drawerItemsPortofolio } from "@/screens/Portofolio/ListPage";
import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer"; import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
import PorfofolioSection from "@/screens/Portofolio/PorfofolioSection"; import Portofolio_SocialMediaSection from "@/screens/Portofolio/SocialMediaSection";
import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio";
import { apiUser } from "@/service/api-client/api-user";
import { GStyles } from "@/styles/global-styles"; import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons"; import { openInDeviceMaps } from "@/utils/openInDeviceMaps";
import { Stack, useLocalSearchParams, router } from "expo-router"; import { FontAwesome, Ionicons } from "@expo/vector-icons";
import { useState } from "react"; import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
import { TouchableOpacity } from "react-native"; import { TouchableOpacity } from "react-native";
export default function Portofolio() { export default function Portofolio() {
const { user } = useAuth();
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false); const [isLoadingDelete, setIsLoadingDelete] = useState(false);
const [data, setData] = useState<any>();
const [profileId, setProfileId] = useState<any>();
const [openDrawerLocation, setOpenDrawerLocation] = useState(false);
const openDrawer = () => { const openDrawer = () => {
setIsDrawerOpen(true); setIsDrawerOpen(true);
@@ -22,15 +44,35 @@ export default function Portofolio() {
const closeDrawer = () => { const closeDrawer = () => {
setIsDrawerOpen(false); setIsDrawerOpen(false);
}; };
useFocusEffect(
useCallback(() => {
onLoadData(id as string);
onLoadUserByToken();
}, [id])
);
async function onLoadData(id: string) {
const response = await apiGetOnePortofolio({ id: id });
setData(response.data);
}
const onLoadUserByToken = async () => {
const response = await apiUser(user?.id as string);
setProfileId(response?.data?.Profile?.id);
};
return ( return (
<> <>
<ViewWrapper>
{/* Header */} {/* Header */}
<Stack.Screen <Stack.Screen
options={{ options={{
title: "Portofolio", title: "Portofolio",
headerLeft: () => <LeftButtonCustom />, headerLeft: () => <LeftButtonCustom />,
headerRight: () => ( headerRight: () =>
data?.Profile?.id !== profileId ? null : (
<TouchableOpacity onPress={openDrawer}> <TouchableOpacity onPress={openDrawer}>
<Ionicons <Ionicons
name="ellipsis-vertical" name="ellipsis-vertical"
@@ -43,36 +85,129 @@ export default function Portofolio() {
headerTitleStyle: GStyles.headerTitleStyle, headerTitleStyle: GStyles.headerTitleStyle,
}} }}
/> />
<PorfofolioSection setShowDeleteAlert={setDeleteAlert} /> <ViewWrapper>
{!data || !profileId ? (
<LoaderCustom />
) : (
<StackCustom>
<Portofolio_Data
data={data}
listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]}
/>
<Portofolio_BusinessLocation
data={data?.BusinessMaps}
imageId={data?.logoId}
setOpenDrawerLocation={setOpenDrawerLocation}
/>
<Portofolio_SocialMediaSection
data={data?.Portofolio_MediaSosial}
/>
{data?.Profile?.id !== profileId ? null : (
<Portofolio_ButtonDelete
id={id as string}
isLoadingDelete={isLoadingDelete}
setIsLoadingDelete={setIsLoadingDelete}
/>
)}
<Spacing />
</StackCustom>
)}
</ViewWrapper> </ViewWrapper>
{/* Drawer Komponen Eksternal */} {/* Drawer Komponen Eksternal */}
<DrawerCustom <DrawerCustom
isVisible={isDrawerOpen} isVisible={isDrawerOpen}
closeDrawer={closeDrawer} closeDrawer={closeDrawer}
height={350} height={"auto"}
> >
<Portofolio_MenuDrawerSection <Portofolio_MenuDrawerSection
drawerItems={drawerItemsPortofolio({ id: id as string })} drawerItems={drawerItemsPortofolio({
id: id as string,
maps: data?.BusinessMaps,
})}
setIsDrawerOpen={setIsDrawerOpen} setIsDrawerOpen={setIsDrawerOpen}
/> />
</DrawerCustom> </DrawerCustom>
{/* Alert Delete */} {/* Drawer Lokasi */}
<AlertCustom <DrawerCustom
isVisible={deleteAlert} isVisible={openDrawerLocation}
onLeftPress={() => setDeleteAlert(false)} closeDrawer={() => setOpenDrawerLocation(false)}
onRightPress={() => { height={"auto"}
setDeleteAlert(false); >
console.log("Hapus portofolio"); <DummyLandscapeImage
router.back(); height={200}
}} imageId={data?.BusinessMaps?.imageId}
title="Hapus Portofolio"
message="Apakah Anda yakin ingin menghapus portofolio ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/> />
<Spacing />
<StackCustom gap={"xs"}>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<FontAwesome
name="building-o"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.BusinessMaps?.namePin}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="list-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={
<TextCustom>{data?.MasterBidangBisnis?.name}</TextCustom>
}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="call-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.tlpn}</TextCustom>}
/>
<GridTwoView
spanLeft={2}
spanRight={10}
leftIcon={
<Ionicons
name="location-outline"
size={ICON_SIZE_SMALL}
color="white"
/>
}
rightIcon={<TextCustom>{data?.alamatKantor}</TextCustom>}
/>
<Spacing />
<ButtonCustom
onPress={() => {
openInDeviceMaps({
latitude: data?.BusinessMaps?.latitude,
longitude: data?.BusinessMaps?.longitude,
title: data?.BusinessMaps?.namePin,
});
}}
>
Buka Maps
</ButtonCustom>
</StackCustom>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -1,47 +1,28 @@
import { BaseBox, Grid, TextCustom, ViewWrapper } from "@/components"; import { TextCustom, ViewWrapper } from "@/components";
import { MainColor } from "@/constants/color-palet"; import Portofolio_BoxView from "@/screens/Portofolio/BoxPortofolioView";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { apiGetPortofolio } from "@/service/api-client/api-portofolio";
import { Ionicons } from "@expo/vector-icons"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { router, useLocalSearchParams } from "expo-router"; import { useCallback, useState } from "react";
export default function ListPortofolio() { export default function ListPortofolio() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [data, setData] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadPortofolio(id as string);
}, [id])
);
const onLoadPortofolio = async (id: string) => {
const response = await apiGetPortofolio({ id: id });
setData(response.data);
};
return ( return (
<ViewWrapper> <ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => ( {data ? data?.map((item: any, index: number) => (
<BaseBox <Portofolio_BoxView key={index} data={item} />
key={index} )) : <TextCustom>Tidak ada portofolio</TextCustom>}
style={{ backgroundColor: MainColor.darkblue }}
onPress={() => {
console.log("press to Portofolio");
router.push(`/portofolio/${id}`);
}}
>
<Grid>
<Grid.Col
span={10}
style={{ justifyContent: "center", backgroundColor: "" }}
>
<TextCustom bold size="large" truncate={1}>
Nama usaha portofolio
</TextCustom>
<TextCustom size="small" color="yellow">
#id-porofolio12345
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
<Ionicons
name="caret-forward"
size={ICON_SIZE_SMALL}
color="white"
/>
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper> </ViewWrapper>
); );
} }

Some files were not shown because too many files have changed in this diff Show More