Compare commits

...

43 Commits

Author SHA1 Message Date
56d074260e Invesment
Add:
- dummy-data-not-publish
- BoxDetailDataSection

Fix:
- investment/[id]/[status]/detail: fix basic ui

## No Issue
2025-07-31 10:41:16 +08:00
b3be6a7f53 Invesment
Add:
- app/(application)/(user)/investment/[id]/[file]/
- app/(application)/(user)/investment/[id]/add-document.tsx
- app/(application)/(user)/investment/[id]/edit-document.tsx
- app/(application)/(user)/investment/[id]/edit-prospectus.tsx
- app/(application)/(user)/investment/[id]/recap-of-document.tsx

Fix:
-  app/(application)/(user)/investment/[id]/list-of-document.tsx
-  app/(application)/(user)/investment/[id]/edit.tsx
-  app/(application)/(user)/investment/[id]/[status]/detail.tsx

## No Issue
2025-07-30 16:55:37 +08:00
c863c04fb4 Invesment
Add :
- screens/Invesment/
- app/(application)/(user)/investment/[id]/

Fix:
- index & portofolio: basic UI

## No Issue
2025-07-30 14:31:39 +08:00
a43ddaa9d6 Invesment
Fix:
- styles/global-styles.ts : tambah alignSelfFlexEnd
- my-holding: basic ui
- portofolio.: basic ui
- transaction: basic ui

## No Issue
2025-07-30 11:15:57 +08:00
927db87749 Component
Add:
- upload button : masih percobaan

Utils:
Add:
- file validasi untuk upload file

Pakage
Add:
- expo-document-picker
- expo-file-system

## No Issue
2025-07-30 10:39:54 +08:00
8a514d2670 Investasi
Add:
- lib/dummy-data/investment : list master
- app/(application)/(user)/investment/(tabs)

Main Layout:
Fix:
- app/(application)/(user)/investment/create.tsx

Component
Add:
- Progress

## No Issue
2025-07-29 17:22:11 +08:00
554428b7b4 Crowd
Add:
-  assets/images/constants/crowd-hipmi.png
-  app/(application)/(user)/crowdfunding/
-  app/(application)/(user)/investment/
-  app/(application)/(user)/donation/

Fix:
-  screens/Home/topFeatureSection.tsx
-  app/(application)/(user)/_layout.tsx

## No Issue
2025-07-29 11:41:41 +08:00
befbd1a47d Voting:
Add:
- ComponentDetailDataSection : Tampilan judul, deskripsi, batas waktu
- BoxDetailHistorySection
- (user)/voting/[id]/history

Fix:
Perbaikan component detail data pada:
- screens/Voting/BoxDetailSection.tsx
-  screens/Voting/BoxDetailPublishSection.tsx
- screens/Voting/BoxDetailContribution.tsx

# No Issue
2025-07-29 10:50:24 +08:00
c9a1ac1db5 Voting
Add:
- BoxDetailContribution
- app/(application)/(user)/voting/[id]/contribution.tsx

Fix:
- app/(application)/(user)/voting/(tabs)/contribution.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- app/(application)/(user)/voting/[id]/list-of-contributor.tsx
- app/(application)/(user)/voting/[id]/index.tsx

# No Issue
2025-07-28 17:29:52 +08:00
4bcfcb5f5a Icon:
Add:
- IconArchive

Voting
Add:
- detail voting
- BoxDetailHasilVotingSection
- BoxDetailPublishSection
- BoxDetailSection
- ButtonStatusSection

Fix:
- BoxPublishSection
- ReportListSection: Hapus import useless

# No Issue
2025-07-28 16:43:54 +08:00
f21ff744d3 Component
Add:
- TabsTwoHeaderCustom

Voting
Fix:
- history

# Np Issue
2025-07-28 14:51:41 +08:00
b18044f53f Component
Add:
- Badge
- Container

Voting
Add:
- screens/Voting

Fix:
- (tabs)/ index, contribution, status

# No Issue
2025-07-28 12:24:35 +08:00
20258d1fe5 Component
Icon:
- IconContribution
- IconHistory

Voting
Add:
- voting (tabs)
- (user)/_layout : penambahan layout voting

# No Issue
2025-07-25 16:58:06 +08:00
51d696128e Component:
Add: components/_ShareComponent/DummyLandscapeImage.

Job
Add:
- edit & status per id

- BoxDetailSectio
- ButtonStatusSection

Fix:
- index, status, archive: penyesuaian ui

# No Issue
2025-07-25 15:32:10 +08:00
1b1732c7d8 Job
Add : app/(application)/(user)/job/[id]/

Fix:
-  app/(application)/(user)/job/(tabs)/archive.tsx
-  app/(application)/(user)/job/(tabs)/index.tsx
-  app/(application)/(user)/job/(tabs)/status.tsx
-  app/(application)/(user)/job/create.tsx

Package:
Add: expo-clipboard

# No Issue
2025-07-25 14:19:57 +08:00
7528c449eb Component
Add:
-  components/_ShareComponent/SearchInput.tsx

Job
Add:
-  screens/Job/
-  app/(application)/(user)/job/(tabs)/index.tsx

Forum:
Fix:
- app/(application)/(user)/job/(tabs)/index: Component search input terpusat

# No Issue
2025-07-25 11:19:11 +08:00
ed87d4a3f3 Job:
Add:
- app/(application)/(user)/job/

Event:
Fix:
- app/(application)/(user)/event/(tabs)/_layout.tsx : penggunaan icon terpusat

Collaboration:
Fix:
- app/(application)/(user)/collaboration/(tabs)/_layout.tsx : penggunaan icon terpusat

Home
Fix:
- Penambahan onPres ke job

Component
Add:
- Icon: home, status

# No Issue
2025-07-25 10:53:31 +08:00
603003865b Collaboration
Add:
- (user)/collaboration/[id]/[detail]/

Fix:
- collaboration/(tabs)/group

# No Issue
2025-07-24 17:42:16 +08:00
e02ae8e35d Component
Add :
- CheckboxGroup
- components/_Icon/

Fix:
- CheckboxCustom: penambahan fitur untuk checkbox group

Feature:
Collaboration
Add :
- ProjectMainSelectedSection

Fix :
- detail-participant
- detail-project-main

# No Issue
2025-07-24 16:47:16 +08:00
4f8ae2d7e0 Component
Add :
- Checkbox

Fix :
- Drawer: tinggi bisa auto
- AvataraAndOtherHeaderComponent : View pembungkus di hapus

Feature : Collaboration
Fix:
- detail-participant
- detail-project-main
(fix tampilan)

# No Issue
2025-07-24 15:00:35 +08:00
360ac5807c Component
Add:
- Contoh Flatlist untuk tampilan yang membutuhkan load data

# No Issue
2025-07-24 11:53:47 +08:00
8cb0054580 fix
Feature:
Add :
- Collaboration/BoxDetailSection
- detail-participant
-
/detail-project-main

Fix:
- participant

# No Issue
2025-07-24 10:25:33 +08:00
64d5a4308c Component
Add:
- Divider

Collaboration
Fix:
- index

Add:
- list pf participants

# No Issue
2025-07-23 16:40:04 +08:00
30d61c84aa Feature:
Add:
- Component alert system
- Page collaboration Id

Fix:
- Box base
- Menu drawer dibuatkan interface

# No Issue '
2025-07-23 15:40:53 +08:00
70e324e76e Fix:
- nama page > participant

Component
Fix:
- Base bos > clean code

# No Issue
2025-07-23 12:09:38 +08:00
4474b46ff3 Collacoration
Add :
- collaboration page
- collaboration component

Component
Fix:
- Base box : tambah props background

# No Issue
2025-07-23 11:31:58 +08:00
aa4ea9fb0c deskripsi:
Fix: ProfileSection & /dummy-user ( image )
Event: box publisj

# No Issue"
2025-07-23 10:25:40 +08:00
81d86885f4 Deskripsi
Event:
Add  app/(application)/(user)/event/[id]/history

# No Issue"
2025-07-22 15:20:53 +08:00
814f261881 Deskripsi:
Event:
Add history detail
Fix : - nama variabel pada [id]/

Component
Fix:
- Spacing : tipe data num | str
- ScrollView : tambah background

# No Issue "
2025-07-22 14:56:29 +08:00
7889d25a44 Deskripsi:
Event:
Add  app/(application)/(user)/event/(tabs)/contribution.tsx
     app/(application)/(user)/event/(tabs)/history.tsx

Fix modified:   app/(application)/(user)/event/(tabs)/_layout.tsx
        deleted:    app/(application)/(user)/event/(tabs)/kontribusi.tsx
        deleted:    app/(application)/(user)/event/(tabs)/riwayat.tsx
    modified:   app/(application)/(user)/event/[id]/publish.tsx

# No Issue
2025-07-22 12:14:08 +08:00
ce0e82e627 Deskripsi:
Event:
Add file:
- contibution with id
- list of participan
- publish
- BoxDetailPublishSection
- BoxPublishSection
- menuDrawerPublish

Fix:
Event:
- layout dan index\

# No Issue
2025-07-22 12:06:54 +08:00
08dfd62bfd Deskripsi:
Event:
Add menu drawer draft
Fix event layoutk, index (beranda) , kontribusi

Comp
Add share avatar-username

# No Issue
2025-07-22 10:35:47 +08:00
c8cc0f0232 deskripsi
Fix: page event/id/deteil menjadi event/id/status
Feature:
Add  screens/Event/AlertButtonStatusSection
Add app/(application)/(user)/event/[id]/[status]/

# No Issue"
2025-07-21 17:39:37 +08:00
b844a8151d deskripsi:
Fix event: layout dan event deatil
Feature: Button dot, edit screen

# No Issue
2025-07-21 15:23:18 +08:00
f9e96aa077 Deskripsi:
Fix: event beranda dan status > tampilan
Feature: create event dan halaman detail status
Fix: Basebox: Hide safearea kalau ada tabs
Fix: TextCustom: Tambah size xlarge
Fix: ScrollCustom penambhan props value

# No Issue
2025-07-18 16:30:56 +08:00
b8b1efc71e Deskripsi:
- New component: Datetime custom, datetime android, datetime ios
- Fix: event create input datetime

# No Issue
2025-07-18 11:48:24 +08:00
eaf0ebfb0a feature
Deskripsi:
try component: Date input
2025-07-17 17:21:31 +08:00
e68d366d49 Deskripsi:
Fix: ViewWrapper tinggi footer
Style: Constan value untuk tinggi footer per OS

# No Issue
2025-07-15 15:30:11 +08:00
9999f78ed4 deskripsi:
Feat: event create
Fix: tabs event

# No Issue
2025-07-15 15:21:41 +08:00
2be5afe5ca Fix: register text input 2025-07-15 14:15:33 +08:00
24913a9f97 deskripsi:
Fix: portofolio: alert hapus porto
Fix: profile create hapus text input alamat & edit ganti select jenis kelamin
Fix: menu forum
Fix:  button porto

# No Issue
2025-07-15 14:13:08 +08:00
3376336c55 Deskripsi:
- new comp : Radio
- fix comp : Text area > placeholder diatas
- fix page : report forum sudah pakai radio

# No Issue
2025-07-15 12:01:28 +08:00
a0dad5618a feature
Desskripsi:
- new page: other-report-commentar, other-report-posting, report-commentar, report-posting

# No Issue
2025-07-15 11:24:57 +08:00
169 changed files with 8622 additions and 374 deletions

View File

@@ -76,6 +76,13 @@ export default function UserLayout() {
),
}}
/>
<Stack.Screen
name="event/create"
options={{
title: "Tambah Event",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="event/detail/[id]"
@@ -85,6 +92,210 @@ export default function UserLayout() {
}}
/>
<Stack.Screen
name="event/[id]/edit"
options={{
title: "Edit Event",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="event/[id]/list-of-participants"
options={{
title: "Daftar peserta",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Event Section ========= */}
{/* ========== Collaboration Section ========= */}
<Stack.Screen
name="collaboration/(tabs)"
options={{
title: "Collaboration",
headerLeft: () => <BackButton path="/home" />,
}}
/>
<Stack.Screen
name="collaboration/create"
options={{
title: "Tambah Proyek",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="collaboration/[id]/list-of-participants"
options={{
title: "Daftar Partisipan",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="collaboration/[id]/detail-participant"
options={{
title: "Partisipasi Proyek",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="collaboration/[id]/edit"
options={{
title: "Edit Proyek",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Collaboration Section ========= */}
{/* ========== Voting Section ========= */}
<Stack.Screen
name="voting/create"
options={{
title: "Tambah Voting",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="voting/(tabs)"
options={{
title: "Voting",
headerLeft: () => <BackButton path="/home" />,
}}
/>
<Stack.Screen
name="voting/[id]/edit"
options={{
title: "Edit Voting",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="voting/[id]/list-of-contributor"
options={{
title: "Daftar Kontributor",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Voting Section ========= */}
{/* ========== Crowdfunding Section ========= */}
<Stack.Screen
name="crowdfunding/index"
options={{
title: "Crowdfunding",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Crowdfunding Section ========= */}
{/* ========== Investment Section ========= */}
<Stack.Screen
name="investment/(tabs)"
options={{
title: "Investasi",
headerLeft: () => <BackButton path="/crowdfunding" />,
}}
/>
<Stack.Screen
name="investment/create"
options={{
title: "Tambah Investasi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="investment/[id]/index"
options={{
title: "Detail Investasi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="investment/[id]/list-of-document"
options={{
title: "Daftar Dokumen",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="investment/[id]/edit"
options={{
title: "Edit Investasi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="investment/[id]/add-document"
options={{
title: "Tambah Dokumen",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="investment/[id]/edit-document"
options={{
title: "Edit Dokumen",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="investment/[id]/edit-prospectus"
options={{
title: "Edit Prospektus",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Investment Section ========= */}
{/* ========== Donation Section ========= */}
<Stack.Screen
name="donation/create"
options={{
title: "Tambah Donasi",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Donation Section ========= */}
{/* ========== Job Section ========= */}
<Stack.Screen
name="job/create"
options={{
title: "Tambah Job",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="job/(tabs)"
options={{
title: "Job Vacancy",
headerLeft: () => <BackButton path="/home" />,
}}
/>
<Stack.Screen
name="job/[id]/index"
options={{
title: "Detail Job",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="job/[id]/edit"
options={{
title: "Edit Job",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== End Job Section ========= */}
{/* ========== Forum Section ========= */}
<Stack.Screen
name="forum/create"
@@ -104,7 +315,7 @@ export default function UserLayout() {
name="forum/[id]/forumku"
options={{
title: "Forumku",
headerLeft: () => <BackButton icon={'close'} />,
headerLeft: () => <BackButton icon={"close"} />,
}}
/>
<Stack.Screen
@@ -114,6 +325,34 @@ export default function UserLayout() {
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-commentar"
options={{
title: "Laporkan Komentar",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="forum/[id]/other-report-posting"
options={{
title: "Laporkan Diskusi",
headerLeft: () => <BackButton />,
}}
/>
{/* ========== Maps Section ========= */}
<Stack.Screen

View File

@@ -0,0 +1,36 @@
import { IconHome } from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
export default function CollaborationTabsLayout() {
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="participant"
options={{
title: "Partisipan",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="people" color={color} />
),
}}
/>
<Tabs.Screen
name="group"
options={{
title: "Grup",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="chatbox-ellipses" color={color} />
),
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,68 @@
import { BaseBox, Grid, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
import { Feather } from "@expo/vector-icons";
export default function CollaborationGroup() {
return (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/collaboration/${index}/${generateProjectName()}/room-chat`}
>
<Grid>
<Grid.Col span={10}>
<TextCustom bold>{generateProjectName()}</TextCustom>
<TextCustom size="small">2 Anggota</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
<Feather name="chevron-right" size={20} color={MainColor.white} />
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper>
);
}
function generateProjectName() {
const adjectives = [
"Blue",
"Dark",
"Bright",
"Quantum",
"Silent",
"Cyber",
"Epic",
"Golden",
"Shadow",
"Rapid",
];
const nouns = [
"Spark",
"Core",
"Orbit",
"Nest",
"Drive",
"Nova",
"Cloud",
"Blade",
"Matrix",
"Link",
];
const randomAdjective =
adjectives[Math.floor(Math.random() * adjectives.length)];
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
return randomAdjective + randomNoun;
}

View File

@@ -0,0 +1,28 @@
import { FloatingButton, ViewWrapper } from "@/components";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { router } from "expo-router";
export default function CollaborationBeranda() {
return (
<>
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton
onPress={() => {
router.push("/collaboration/create");
}}
/>
}
>
{Array.from({ length: 10 }).map((_, index) => (
<Collaboration_BoxPublishSection
key={index}
id={index.toString()}
href={`/collaboration/${index}`}
/>
))}
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,77 @@
import { ButtonCustom, Spacing } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet";
import Collaboration_BoxPublishSection from "@/screens/Collaboration/BoxPublishSection";
import { useState } from "react";
import { View } from "react-native";
export default function CollaborationParticipans() {
const [activeCategory, setActiveCategory] = useState<string | null>(
"participant"
);
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
const headerComponent = (
<View
style={{
flexDirection: "row",
alignItems: "center",
padding: 5,
backgroundColor: MainColor.soft_darkblue,
borderRadius: 50,
width: "100%",
}}
>
<ButtonCustom
backgroundColor={
activeCategory === "participant" ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === "participant" ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress("participant")}
>
Partisipasi Proyek
</ButtonCustom>
<Spacing width={"2%"} />
<ButtonCustom
backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === "main" ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress("main")}
>
Proyek Saya
</ButtonCustom>
</View>
);
return (
<ViewWrapper hideFooter headerComponent={headerComponent}>
{Array.from({ length: 10 }).map((_, index) => (
<Collaboration_BoxPublishSection
key={index.toString()}
id={index.toString()}
username={` ${
activeCategory === "participant"
? "Partisipasi Proyek"
: "Proyek Saya"
}`}
href={
activeCategory === "participant"
? `/collaboration/${index}/detail-participant`
: `/collaboration/${index}/detail-project-main`
}
/>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
AvatarUsernameAndOtherComponent,
BackButton,
BoxWithHeaderSection,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { Stack, useLocalSearchParams } from "expo-router";
export default function CollaborationRoomInfo() {
const { id, detail } = useLocalSearchParams();
return (
<>
<Stack.Screen
options={{
title: `Info`,
headerLeft: () => <BackButton />,
}}
/>
<ViewWrapper>
<BoxWithHeaderSection>
<StackCustom>
{listData.map((item, index) => (
<Grid key={index}>
<Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{item.value}</TextCustom>
</Grid.Col>
</Grid>
))}
</StackCustom>
</BoxWithHeaderSection>
<BoxWithHeaderSection>
{Array.from({ length: 10 }).map((_, index) => (
<AvatarUsernameAndOtherComponent key={index} avatarHref={`/profile/${index}`} />
))}
</BoxWithHeaderSection>
</ViewWrapper>
</>
);
}
const listData = [
{
title: "Judul Proyek",
value: "Judul Proyek",
},
{
title: "Industri",
value: "Pilihan Industri",
},
{
title: "Deskripsi",
value: "Deskripsi Proyek",
},
{
title: "Tujuan Proyek",
value:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
},
{
title: "Keuntungan Proyek",
value:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
},
];

View File

@@ -0,0 +1,192 @@
import {
BackButton,
BoxButtonOnFooter,
Grid,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Feather } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { StyleSheet, TouchableOpacity, View } from "react-native";
export default function CollaborationRoomChat() {
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 (
<>
<Stack.Screen
options={{
title: `Proyek ${detail}`,
headerLeft: () => <BackButton />,
headerRight: () => (
<Feather
name="info"
size={ICON_SIZE_SMALL}
color={MainColor.yellow}
onPress={() => router.push(`/collaboration/${id}/${detail}/info`)}
/>
),
}}
/>
<ViewWrapper footerComponent={inputChat()}>
{dummyData.map((item, index) => (
<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,62 @@
import {
AvatarUsernameAndOtherComponent,
BaseBox,
DrawerCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { MaterialIcons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function CollaborationDetailParticipant() {
const { id } = useLocalSearchParams();
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
return (
<>
<ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} />
<BaseBox style={{ height: 500 }}>
<TextCustom align="center" bold size="large">
Partisipan
</TextCustom>
{Array.from({ length: 5 }).map((_, index) => (
<AvatarUsernameAndOtherComponent
key={index}
avatarHref={`/profile/${index}`}
rightComponent={
<MaterialIcons
name="notes"
size={ICON_SIZE_SMALL}
color="white"
onPress={() => setOpenDrawerParticipant(true)}
/>
}
/>
))}
</BaseBox>
</ViewWrapper>
<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

@@ -0,0 +1,106 @@
import {
AlertDefaultSystem,
BackButton,
ButtonCustom,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function CollaborationDetailProjectMain() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [selected, setSelected] = useState<(string | number)[]>([]);
const handleEdit = () => {
console.log("Edit collaboration");
router.push("/(application)/(user)/collaboration/(id)/edit");
};
return (
<>
<Stack.Screen
options={{
title: "Proyek Saya",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} />
<Collaboration_MainParticipanSelectedSection
selected={selected}
setSelected={setSelected}
setOpenDrawerParticipant={setOpenDrawerParticipant}
/>
<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 />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Edit",
path: "/(application)/(user)/collaboration/(tabs)/group",
icon: <IconEdit />,
},
]}
onPressItem={(item) => {
handleEdit();
}}
/>
</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

@@ -0,0 +1,53 @@
import {
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
export default function CollaborationEdit() {
return (
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
<SelectCustom
label="Pilih Industri"
data={[
{ label: "Industri 1", value: "industri-1" },
{ label: "Industri 2", value: "industri-2" },
{ label: "Industri 3", value: "industri-3" },
]}
onChange={(value) => console.log(value)}
/>
<TextAreaCustom
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
/>
<ButtonCustom
title="Update"
onPress={() => {
console.log("Update proyek");
router.back();
}}
/>
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,91 @@
import {
AlertDefaultSystem,
BackButton,
ButtonCustom,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function CollaborationDetail() {
const { id } = useLocalSearchParams();
const [openDrawerPartisipasi, setOpenDrawerPartisipasi] = useState(false);
const [openDrawerMenu, setOpenDrawerMenu] = useState(false);
return (
<>
<Stack.Screen
options={{
title: "Detail Proyek",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerMenu(true)} />
),
}}
/>
<ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} />
<ButtonCustom onPress={() => setOpenDrawerPartisipasi(true)}>
Partisipasi
</ButtonCustom>
</ViewWrapper>
{/* Drawer Partisipasi */}
<DrawerCustom
isVisible={openDrawerPartisipasi}
closeDrawer={() => setOpenDrawerPartisipasi(false)}
height={300}
>
<TextAreaCustom
label="Dekripsi diri"
placeholder="Masukan dekripsi diri"
required
showCount
maxLength={500}
/>
<ButtonCustom
style={{ alignSelf: "flex-end" }}
onPress={() => {
AlertDefaultSystem({
title: "Simpan data deskripsi",
message: "Apakah anda sudah yakin ingin menyimpan data ini ?",
textLeft: "Batal",
textRight: "Simpan",
onPressRight: () => router.replace(`/collaboration/(tabs)/group`),
});
}}
>
Simpan
</ButtonCustom>
</DrawerCustom>
{/* Drawer Menu */}
<DrawerCustom
isVisible={openDrawerMenu}
closeDrawer={() => setOpenDrawerMenu(false)}
height={250}
>
<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);
setOpenDrawerMenu(false);
}}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,82 @@
import {
AvatarUsernameAndOtherComponent,
BaseBox,
DrawerCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { Feather } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { ScrollView } from "react-native";
export default function CollaborationListOfParticipants() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
return (
<>
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingBlock={5}>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${id}`}
rightComponent={
<Feather
name="chevron-right"
size={24}
color="white"
onPress={() => setOpenDrawer(true)}
/>
}
/>
</BaseBox>
))}
</ViewWrapper>
{/* Drawer */}
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
>
<StackCustom>
<TextCustom bold>Deskripsi diri</TextCustom>
<BaseBox>
<ScrollView style={{ height: "80%" }}>
<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>
</BaseBox>
<Spacing />
</StackCustom>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,53 @@
import {
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper
} from "@/components";
import { router } from "expo-router";
export default function CollaborationCreate() {
return (
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
<SelectCustom
label="Pilih Industri"
data={[
{ label: "Industri 1", value: "industri-1" },
{ label: "Industri 2", value: "industri-2" },
{ label: "Industri 3", value: "industri-3" },
]}
onChange={(value) => console.log(value)}
/>
<TextAreaCustom
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
/>
<ButtonCustom
title="Simpan"
onPress={() => {
console.log("Simpan proyek");
router.back();
}}
/>
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,68 @@
import {
BaseBox,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Feather } from "@expo/vector-icons";
import { Image } from "expo-image";
export default function Crowdfunding() {
const listPage = [
{
title: "Investasi",
desc: "Buat investasi dan jual beli saham lebih mudah dengan pengguna lain.",
path: "investment/(tabs)",
},
{
title: "Donasi",
desc: "Berbagi info untuk berdonasi lebih luas dan lebih efisien.",
path: "donation/create",
},
];
return (
<ViewWrapper>
<StackCustom>
<Image
source={require("@/assets/images/constants/crowd-hipmi.png")}
contentFit="cover"
transition={1000}
style={{
width: "100%",
height: 200,
borderRadius: 10,
}}
/>
{listPage.map((item, index) => (
<BaseBox key={index} paddingTop={10} paddingBottom={10} href={item.path as any} marginBottom={0}>
<Grid>
<Grid.Col span={10}>
<StackCustom gap={"xs"}>
<TextCustom bold size="large">
{item.title}
</TextCustom>
<TextCustom>{item.desc}</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
<Feather
name="chevron-right"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
</Grid.Col>
</Grid>
</BaseBox>
))}
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,11 @@
import { TextCustom, ViewWrapper } from "@/components";
export default function DonationCreate() {
return (
<ViewWrapper>
<TextCustom bold size="large">
Coming Soon !
</TextCustom>
</ViewWrapper>
);
}

View File

@@ -1,62 +1,41 @@
import { MainColor } from "@/constants/color-palet";
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
import {
IconContribution,
IconHistory,
IconHome,
IconStatus,
} from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Tabs } from "expo-router";
export default function EventLayout() {
export default function EventTabsLayout() {
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: MainColor.yellow,
tabBarInactiveTintColor: MainColor.white_gray,
tabBarStyle: {
backgroundColor: MainColor.darkblue,
},
// tabBarButton: HapticTab,
// tabBarBackground: BlurTabBarBackground,
// tabBarStyle: Platform.select({
// ios: {
// // Use a transparent background on iOS to show the blur effect
// position: "absolute",
// },
// default: {},
// }),
}}
>
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="home" color={color} />
),
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="list" color={color} />
),
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="kontribusi"
name="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="extension-puzzle" color={color} />
),
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="riwayat"
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => (
<FontAwesome5 size={20} name="history" color={color} />
),
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>

View File

@@ -0,0 +1,43 @@
import {
AvatarCustom,
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
Grid,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import React from "react";
export default function EventContribution() {
return (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<BoxWithHeaderSection key={index} href={`/event/${index}/contribution`}>
<StackCustom>
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${index}`}
rightComponent={
<TextCustom truncate>
{new Date().toLocaleDateString()}
</TextCustom>
}
/>
<TextCustom bold align="center" size="xlarge">
Judul Event Disini
</TextCustom>
<Grid>
{Array.from({ length: 4 }).map((_, index2) => (
<Grid.Col span={3} key={index2}>
<AvatarCustom size="sm" href={`/profile/${index2}`} />
</Grid.Col>
))}
</Grid>
</StackCustom>
</BoxWithHeaderSection>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,68 @@
import { ButtonCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { useState } from "react";
import { View } from "react-native";
export default function EventHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
const headerComponent = (
<View
style={{
flexDirection: "row",
alignItems: "center",
padding: 5,
backgroundColor: MainColor.soft_darkblue,
borderRadius: 50,
width: "100%",
}}
>
<ButtonCustom
backgroundColor={
activeCategory === "all" ? MainColor.yellow : AccentColor.blue
}
textColor={activeCategory === "all" ? MainColor.black : MainColor.white}
style={{ width: "49%" }}
onPress={() => handlePress("all")}
>
Semua Riwayat
</ButtonCustom>
<Spacing width={"2%"} />
<ButtonCustom
backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === "main" ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress("main")}
>
Riwayat Saya
</ButtonCustom>
</View>
);
return (
<ViewWrapper headerComponent={headerComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<Event_BoxPublishSection
key={index.toString()}
id={index.toString()}
username={`Riwayat ${activeCategory === "main" ? "Saya" : "Semua"}`}
rightComponentAvatar={
<TextCustom>{new Date().toLocaleDateString()}</TextCustom>
}
href={`/event/${index}/history`}
/>
))}
</ViewWrapper>
);
}

View File

@@ -1,25 +1,23 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import FloatingButton from "@/components/Button/FloatingButton";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { router } from "expo-router";
import { Text, TouchableHighlight, View } from "react-native";
export default function Event() {
export default function EventBeranda() {
return (
<ViewWrapper>
<TouchableHighlight onPress={() => router.push("/event/detail/1")}>
<View
style={{
padding: 20,
backgroundColor: MainColor.darkblue,
borderRadius: 10,
borderColor: AccentColor.blue,
borderWidth: 1,
}}
>
<Text style={GStyles.textLabel}>Event</Text>
</View>
</TouchableHighlight>
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/event/create")} />
}
>
{Array.from({ length: 10 }).map((_, index) => (
<Event_BoxPublishSection
key={index}
id={index.toString()}
href={`/event/${index}/publish`}
/>
))}
</ViewWrapper>
);
}

View File

@@ -1,11 +0,0 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
export default function Kontribusi() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Kontribusi</Text>
</ViewWrapper>
);
}

View File

@@ -1,11 +0,0 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
export default function Riwayat() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Riwayat</Text>
</ViewWrapper>
);
}

View File

@@ -1,11 +1,63 @@
import {
BoxWithHeaderSection,
Grid,
ScrollableCustom,
StackCustom,
TextCustom
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { GStyles } from "@/styles/global-styles";
import { Text } from "react-native";
import { masterStatus } from "@/lib/dummy-data/_master/status";
import { useState } from "react";
export default function EventStatus() {
const id = "test-id-event";
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={masterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
export default function Status() {
return (
<ViewWrapper>
<Text style={GStyles.textLabel}>Status</Text>
<ViewWrapper headerComponent={scrollComponent}>
<BoxWithHeaderSection href={`/event/${id}/${activeCategory}/detail-event`}>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
Lorem ipsum,{" "}
<TextCustom color="green">{activeCategory}</TextCustom> dolor
sit amet consectetur adipisicing elit.
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>{new Date().toLocaleDateString()}</TextCustom>
</Grid.Col>
</Grid>
<TextCustom truncate={2}>
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>
</BoxWithHeaderSection>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,117 @@
import {
BaseBox,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_AlertButtonStatusSection from "@/screens/Event/AlertButtonStatusSection";
import Event_ButtonStatusSection from "@/screens/Event/ButtonStatusSection";
import { menuDrawerDraftEvent } from "@/screens/Event/menuDrawerDraft";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function EventDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openAlert, setOpenAlert] = useState(false);
const [openDeleteAlert, setOpenDeleteAlert] = useState(false);
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail ${status === "publish" ? "" : status}`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
}}
/>
<ViewWrapper>
<BaseBox>
<StackCustom>
<TextCustom bold align="center" size="xlarge">
Judul event {status}
</TextCustom>
{listData.map((item, index) => (
<Grid key={index}>
<Grid.Col span={4}>
<TextCustom bold>{item.title}</TextCustom>
</Grid.Col>
<Grid.Col span={8}>
<TextCustom>{item.value}</TextCustom>
</Grid.Col>
</Grid>
))}
</StackCustom>
</BaseBox>
<Event_ButtonStatusSection
status={status as string}
onOpenAlert={setOpenAlert}
onOpenDeleteAlert={setOpenDeleteAlert}
/>
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={250}
>
<MenuDrawerDynamicGrid
data={menuDrawerDraftEvent({ id: id as string }) as any}
columns={4}
onPressItem={handlePress as any}
/>
</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

@@ -0,0 +1,51 @@
import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
Spacing,
} from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function EventDetailContribution() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail kontribusi`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
<Event_BoxDetailPublishSection />
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={250}
>
<MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })}
columns={4}
onPressItem={handlePress}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,107 @@
import {
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router";
import React, { useState } from "react";
import { Platform } from "react-native";
export default function EventEdit() {
const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null
>(null);
const [selectedEndDate, setSelectedEndDate] = useState<
Date | DateTimePickerEvent | null
>(null);
const handlerSubmit = () => {
try {
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");
}
console.log("Data berhasil terupdate");
router.back()
} catch (error) {
console.log(error);
}
};
const buttonSubmit = (
<ButtonCustom title="Update" onPress={handlerSubmit} />
);
return (
<>
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom
placeholder="Masukkan nama event"
label="Nama Event"
required
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={masterTypeEvent}
onChange={(value) => console.log(value)}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
/>
<DateTimePickerCustom
label="Tanggal & Waktu Mulai"
required
onChange={(date: Date) => {
setSelectedDate(date as any);
}}
value={selectedDate as any}
minimumDate={new Date(Date.now())}
/>
<DateTimePickerCustom
label="Tanggal & Waktu Berakhir"
required
onChange={(date: Date) => {
setSelectedEndDate(date as any);
}}
value={selectedEndDate as any}
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
maxLength={100}
/>
{buttonSubmit}
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,51 @@
import {
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
Spacing,
} from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function EventDetailHistory() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail riwayat`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
<Event_BoxDetailPublishSection />
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={250}
>
<MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })}
columns={4}
onPressItem={handlePress}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,17 @@
import {
AvatarUsernameAndOtherComponent,
BaseBox,
ViewWrapper,
} from "@/components";
export default function EventListOfParticipants() {
return (
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingBlock={0}>
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${index}`} />
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,64 @@
import {
ButtonCustom,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper
} from "@/components";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import LeftButtonCustom from "@/components/Button/BackButton";
import Event_BoxDetailPublishSection from "@/screens/Event/BoxDetailPublishSection";
import { menuDrawerPublishEvent } from "@/screens/Event/menuDrawerPublish";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { Alert } from "react-native";
export default function EventDetailPublish() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
};
const footerButton = (
<ButtonCustom
backgroundColor="green"
textColor="white"
onPress={() => Alert.alert("Anda berhasil join event ini")}
>
Join
</ButtonCustom>
);
return (
<>
<Stack.Screen
options={{
title: `Event publish`,
headerLeft: () => <LeftButtonCustom />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
<Event_BoxDetailPublishSection footerButton={footerButton} />
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={250}
>
<MenuDrawerDynamicGrid
data={menuDrawerPublishEvent({ id: id as string })}
columns={4}
onPressItem={handlePress}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,109 @@
import {
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router";
import React, { useState } from "react";
import { Platform } from "react-native";
export default function EventCreate() {
const [selectedDate, setSelectedDate] = useState<
Date | DateTimePickerEvent | null
>(null);
const [selectedEndDate, setSelectedEndDate] = useState<
Date | DateTimePickerEvent | null
>(null);
const handlerSubmit = () => {
try {
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");
}
console.log("Data berhasil disimpan");
router.navigate("/event/status");
} catch (error) {
console.log(error);
}
};
const buttonSubmit = (
<ButtonCustom title="Simpan" onPress={handlerSubmit} />
// <BoxButtonOnFooter>
// </BoxButtonOnFooter>
);
return (
<>
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom
placeholder="Masukkan nama event"
label="Nama Event"
required
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={masterTypeEvent}
onChange={(value) => console.log(value)}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
/>
<DateTimePickerCustom
label="Tanggal & Waktu Mulai"
required
onChange={(date: Date) => {
setSelectedDate(date as any);
}}
value={selectedDate as any}
minimumDate={new Date(Date.now())}
/>
<DateTimePickerCustom
label="Tanggal & Waktu Berakhir"
required
onChange={(date: Date) => {
setSelectedEndDate(date as any);
}}
value={selectedEndDate as any}
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
maxLength={100}
/>
{buttonSubmit}
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -11,7 +11,7 @@ import {
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
import { listDataDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
@@ -47,12 +47,14 @@ export default function Forumku() {
</ButtonCustom>
</Grid.Col>
</Grid>
{listDataDummyCommentarForum.map((e, i) => (
{listDummyDiscussionForum.map((e, i) => (
<Forum_BoxDetailSection
key={i}
data={e}
setOpenDrawer={setOpenDrawer}
setStatus={setStatus}
isTruncate={true}
href={`/forum/${id}`}
/>
))}
</StackCustom>

View File

@@ -3,7 +3,6 @@ import {
ButtonCustom,
DrawerCustom,
Spacing,
StackCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
@@ -12,9 +11,9 @@ import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
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_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { Divider } from "react-native-paper";
export default function ForumDetail() {
const { id } = useLocalSearchParams();
@@ -27,7 +26,7 @@ export default function ForumDetail() {
// Comentar
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
const [statusCommentar, setStatusCommentar] = useState("");
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false);
const dataDummy = {
name: "Bagas",
@@ -78,7 +77,6 @@ export default function ForumDetail() {
key={i}
data={e}
setOpenDrawer={setOpenDrawerCommentar}
setStatus={setStatusCommentar}
/>
))}
</ViewWrapper>
@@ -145,16 +143,34 @@ export default function ForumDetail() {
isVisible={openDrawerCommentar}
closeDrawer={() => setOpenDrawerCommentar(false)}
>
<Forum_MenuDrawerBerandaSection
<Forum_MenuDrawerCommentar
id={id as string}
status={statusCommentar}
setIsDrawerOpen={() => {
setOpenDrawerCommentar(false);
}}
setShowDeleteAlert={setDeleteAlert}
setShowAlertStatus={setAlertStatus}
setShowDeleteAlert={setAlertDeleteCommentar}
/>
</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

@@ -0,0 +1,32 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router";
export default function ForumOtherReportCommentar() {
const handleSubmit = (
<BoxButtonOnFooter>
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report lainnya");
router.back();
}}
>
Report
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Komentar" />
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,32 @@
import {
BoxButtonOnFooter,
ButtonCustom,
TextAreaCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { router } from "expo-router";
export default function ForumOtherReportPosting() {
const handleSubmit = (
<BoxButtonOnFooter>
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report lainnya");
router.back();
}}
>
Report
</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={handleSubmit}>
<TextAreaCustom placeholder="Laporkan Diskusi" />
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,42 @@
import {
ButtonCustom,
Spacing,
StackCustom,
ViewWrapper
} from "@/components";
import { AccentColor, MainColor } from "@/constants/color-palet";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router";
export default function ForumReportCommentar() {
return (
<>
<ViewWrapper>
<StackCustom>
<Forum_ReportListSection />
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report");
router.back();
}}
>
Report
</ButtonCustom>
<ButtonCustom
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
console.log("Lainnya");
router.replace("/forum/[id]/other-report-commentar");
}}
>
Lainnya
</ButtonCustom>
<Spacing/>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,37 @@
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components";
import { MainColor, AccentColor } from "@/constants/color-palet";
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
import { router } from "expo-router";
export default function ForumReportPosting() {
return (
<>
<ViewWrapper>
<StackCustom>
<Forum_ReportListSection />
<ButtonCustom
backgroundColor={MainColor.red}
textColor={MainColor.white}
onPress={() => {
console.log("Report");
router.back();
}}
>
Report
</ButtonCustom>
<ButtonCustom
backgroundColor={AccentColor.blue}
textColor={MainColor.white}
onPress={() => {
console.log("Lainnya");
router.replace("/forum/[id]/other-report-posting");
}}
>
Lainnya
</ButtonCustom>
<Spacing />
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -3,16 +3,14 @@ import {
AvatarCustom,
BackButton,
DrawerCustom,
TextInputCustom,
SearchInput,
ViewWrapper,
} from "@/components";
import FloatingButton from "@/components/Button/FloatingButton";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
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 { Ionicons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import { useState } from "react";
@@ -34,20 +32,7 @@ export default function Forum() {
/>
<ViewWrapper
headerComponent={
<TextInputCustom
iconLeft={
<Ionicons
name="search-outline"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari topik forum..."
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
}
headerComponent={<SearchInput placeholder="Cari topik diskusi" />}
floatingButton={
<FloatingButton
onPress={() =>

View File

@@ -0,0 +1,59 @@
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { TabsStyles } from "@/styles/tabs-styles";
import { Feather, FontAwesome6, Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
export default function InvestmentTabsLayout() {
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Bursa",
tabBarIcon: ({ color }) => (
<Ionicons
name="bar-chart-outline"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
<Tabs.Screen
name="portofolio"
options={{
title: "Portofolio",
tabBarIcon: ({ color }) => (
<Feather name="pie-chart" color={color} size={ICON_SIZE_SMALL} />
),
}}
/>
<Tabs.Screen
name="my-holding"
options={{
title: "Saham Saya",
tabBarIcon: ({ color }) => (
<FontAwesome6
name="hand-holding-dollar"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
<Tabs.Screen
name="transaction"
options={{
title: "Transaksi",
tabBarIcon: ({ color }) => (
<FontAwesome6
name="money-bill-transfer"
color={color}
size={ICON_SIZE_SMALL}
/>
),
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,81 @@
import {
BaseBox,
FloatingButton,
Grid,
ProgressCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import dayjs from "dayjs";
import { Image } from "expo-image";
import { router } from "expo-router";
import { View } from "react-native";
export default function InvestmentBursa() {
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/investment/create")} />
}
>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingTop={7} paddingBottom={7} href={`/investment/${index}`}>
<Grid>
<Grid.Col span={5}>
<Image
source={DUMMY_IMAGE.background}
style={{ width: "auto", height: 100, borderRadius: 10 }}
/>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={6}>
<StackCustom>
<TextCustom truncate={2}>
Title here : Lorem ipsum dolor sit amet consectetur
adipisicing elit. Omnis, exercitationem, sequi enim quod
distinctio maiores laudantium amet, quidem atque repellat sit
vitae qui aliquam est veritatis laborum eum voluptatum totam!
</TextCustom>
<ProgressCustom value={index % 5 * 20} size="lg" />
<TextCustom>
Sisa waktu: {dayjs().diff(dayjs(), "day")} hari
</TextCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper>
);
}
// <View style={{ padding: 20, gap: 16 }}>
// <TextCustom>Progress 70%</TextCustom>
// <ProgressCustom value={70} color="primary" size="md" />
// <TextCustom>Success Progress</TextCustom>
// <ProgressCustom value={40} color="success" size="lg" />
// <TextCustom>Warning Progress (small)</TextCustom>
// <ProgressCustom value={90} color="warning" size="sm" />
// <TextCustom>Error Indeterminate</TextCustom>
// <ProgressCustom value={null} color="error" size="md" />
// <TextCustom>Custom Radius</TextCustom>
// <ProgressCustom value={60} color="info" size="xl" radius={4} />
// <ProgressCustom value={70} color="primary" size="lg" />
// <ProgressCustom value={45} color="success" size="md" label="Halfway!" />
// <ProgressCustom value={90} color="warning" size="lg" showLabel={false} />
// <ProgressCustom value={null} color="error" size="sm" label="Loading..." />
// </View>;

View File

@@ -0,0 +1,49 @@
import {
BaseBox,
Grid,
ProgressCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { View } from "react-native";
export default function InvestmentMyHolding() {
return (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox key={index} paddingTop={7} paddingBottom={7}>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate={2}>
Title here : Lorem ipsum dolor sit amet consectetur
adipisicing elit. Omnis, exercitationem, sequi enim quod
distinctio maiores laudantium amet, quidem atque repellat sit
vitae qui aliquam est veritatis laborum eum voluptatum totam!
</TextCustom>
<Spacing height={5} />
<TextCustom size="small">Rp. 7.500.000</TextCustom>
<TextCustom size="small">300 Lembar</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col
span={5}
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<ProgressCustom value={(index % 5) * 20} size="lg" />
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,39 @@
import { ScrollableCustom, ViewWrapper } from "@/components";
import { masterStatus } from "@/lib/dummy-data/_master/status";
import Investment_StatusBox from "@/screens/Invesment/StatusBox";
import { useState } from "react";
export default function InvestmentPortofolio() {
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={masterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, index) => (
<Investment_StatusBox
key={index}
id={index.toString()}
status={activeCategory as string}
href={`/investment/${index}/${activeCategory}/detail`}
/>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,53 @@
import {
BadgeCustom,
BaseBox,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs";
import { View } from "react-native";
export default function InvestmentTransaction() {
return (
<ViewWrapper hideFooter>
{Array.from({ length: 10 }).map((_, i) => (
<BaseBox key={i} paddingTop={7} paddingBottom={7}>
<Grid>
<Grid.Col span={6}>
<StackCustom gap={"xs"}>
<TextCustom truncate>
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">
{dayjs().format("DD/MM/YYYY")}
</TextCustom>
</StackCustom>
</Grid.Col>
<Grid.Col span={1}>
<View />
</Grid.Col>
<Grid.Col span={5} style={{ alignItems: "flex-end" }}>
<StackCustom gap={"xs"}>
<TextCustom bold truncate>
Rp. 7.500.000
</TextCustom>
<BadgeCustom
variant="light"
color="success"
style={GStyles.alignSelfFlexEnd}
>
Berhasil
</BadgeCustom>
</StackCustom>
</Grid.Col>
</Grid>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,20 @@
import { BackButton, TextCustom, ViewWrapper } from "@/components";
import { Stack, useLocalSearchParams } from "expo-router";
import _ from "lodash";
export default function InvestmentProspectus() {
const { file } = useLocalSearchParams();
return (
<>
<Stack.Screen
options={{
title: `Pratinjau ${_.startCase(file as string)}`,
headerLeft: () => <BackButton />,
}}
/>
<ViewWrapper>
<TextCustom>Pratinjau File</TextCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,203 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
BackButton,
BaseBox,
CenterCustom,
DotButton,
DrawerCustom,
Grid,
MenuDrawerDynamicGrid,
ProgressCustom,
Spacing,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM, ICON_SIZE_SMALL } from "@/constants/constans-value";
import { listDataNotPublish } from "@/lib/dummy-data/investment/dummy-data-not-publish";
import BoxDetailDataSection from "@/screens/Invesment/BoxDetailDataSection";
import Investment_ButtonStatusSection from "@/screens/Invesment/ButtonStatusSection";
import {
AntDesign,
FontAwesome6,
Ionicons,
MaterialIcons,
} from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useState } from "react";
export default function InvestmentDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
setOpenDrawerDraft(false);
};
const handlePressPublish = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
setOpenDrawerPublish(false);
};
const bottomSection = (
<Grid>
<Grid.Col span={6} style={{ paddingRight: 10 }}>
<BaseBox
backgroundColor={AccentColor.blue}
style={{ borderColor: AccentColor.softblue, borderWidth: 1 }}
href={`/investment/${id}/prospektus/file`}
>
<StackCustom>
<TextCustom align="center">Prospektus</TextCustom>
<CenterCustom>
<FontAwesome6
name="file-contract"
size={50}
color={MainColor.white}
/>
</CenterCustom>
</StackCustom>
</BaseBox>
</Grid.Col>
<Grid.Col span={6} style={{ paddingLeft: 10 }}>
<BaseBox
backgroundColor={AccentColor.blue}
style={{ borderColor: AccentColor.softblue, borderWidth: 1 }}
href={`/investment/${id}/list-of-document`}
>
<StackCustom>
<TextCustom align="center">Dokumen</TextCustom>
<CenterCustom>
<FontAwesome6
name="file-lines"
size={50}
color={MainColor.white}
/>
</CenterCustom>
</StackCustom>
</BaseBox>
</Grid.Col>
</Grid>
);
return (
<>
<Stack.Screen
options={{
title: `Detail ${_.startCase(status as string)}`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
}}
/>
<ViewWrapper>
<StackCustom gap={"sm"}>
{status === "publish" && (
<BaseBox>
<StackCustom>
<TextCustom bold>Progress Saham</TextCustom>
<ProgressCustom value={70} size="lg" />
</StackCustom>
</BaseBox>
)}
<BoxDetailDataSection
data={listDataNotPublish}
bottomSection={bottomSection}
/>
<Investment_ButtonStatusSection status={status as string} />
<Spacing />
</StackCustom>
</ViewWrapper>
{/* ========= Draft Drawer ========= */}
<DrawerCustom
isVisible={openDrawerDraft}
closeDrawer={() => setOpenDrawerDraft(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconEdit />,
label: "Edit Data",
path: `/investment/${id}/edit`,
},
{
icon: (
<AntDesign
name="edit"
size={ICON_SIZE_MEDIUM}
color={MainColor.white}
/>
),
label: "Edit Prospektus",
path: `/investment/${id}/edit-prospectus`,
},
{
icon: (
<MaterialIcons
name="create"
size={ICON_SIZE_MEDIUM}
color={MainColor.white}
/>
),
label: "Update Dokumen",
path: `/investment/${id}/recap-of-document`,
},
]}
columns={4}
onPressItem={handlePressDraft as any}
/>
</DrawerCustom>
{/* ========= Publish Drawer ========= */}
<DrawerCustom
isVisible={openDrawerPublish}
closeDrawer={() => setOpenDrawerPublish(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: (
<FontAwesome6
name="file-lines"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
label: "Update Dokumen",
path: `/investment/${id}/recap-of-document`,
},
{
icon: (
<Ionicons
name="newspaper-outline"
size={ICON_SIZE_MEDIUM}
color={MainColor.white}
/>
),
label: "Update Berita",
path: `/investment/${id}/edit`,
},
]}
columns={4}
onPressItem={handlePressPublish as any}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,58 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
export default function InvestmentAddDocument() {
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Simpan</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<StackCustom gap={"xs"}>
<InformationBox text="File dokumen bersifat opsional, jika memang ada file yang bisa membantu meyakinkan investor. Anda bisa mengupload nya." />
<Spacing />
<TextInputCustom
label="Judul Dokumen"
placeholder="Masukan judul dokumen"
required
/>
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() =>
router.push("/(application)/(image)/take-picture/123")
}
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,58 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
export default function InvestmentEditDocument() {
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonFooter}>
<StackCustom gap={"xs"}>
<InformationBox text="File dokumen bersifat opsional, jika memang ada file yang bisa membantu meyakinkan investor. Anda bisa mengupload nya." />
<Spacing />
<TextInputCustom
label="Judul Dokumen"
placeholder="Masukan judul dokumen"
required
/>
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() =>
router.push("/(application)/(image)/take-picture/123")
}
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</ViewWrapper>
</>
);
}

View File

@@ -0,0 +1,46 @@
import {
BaseBox,
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
Spacing,
StackCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
export default function InvestmentEditProspectus() {
const buttonFooter = (
<BoxButtonOnFooter>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
</BoxButtonOnFooter>
);
return (
<ViewWrapper footerComponent={buttonFooter}>
<StackCustom gap={"xs"}>
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepan." />
<Spacing />
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() => router.push("/(application)/(image)/take-picture/123")}
>
Upload
</ButtonCenteredOnly>
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,164 @@
import {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper
} from "@/components";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
import { router } from "expo-router";
import { useState } from "react";
export default function InvestmentEdit() {
const [data, setData] = useState({
title: "",
targetDana: 0,
hargaPerLembar: 0,
totalLembar: 0,
rasioKeuntungan: 0,
pencarianInvestor: "",
periodeDeviden: "",
pembagianDeviden: "",
});
return (
<ViewWrapper>
<StackCustom gap={"xs"}>
<InformationBox text="Gambar investasi bisa berupa ilustrasi, poster atau foto terkait investasi." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.push("/take-picture/1")}
>
Upload
</ButtonCenteredOnly>
<Spacing />
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya." />
<TextInputCustom
required
placeholder="Judul"
label="Judul"
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<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
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
required
iconLeft="Rp."
placeholder="0"
label="Harga Per Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/>
<TextInputCustom
required
placeholder="0"
label="Total Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, totalLembar: Number(value) })
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/>
<TextInputCustom
required
iconRight="%"
label="Rasio Keuntungan / ROI %"
placeholder="0"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, rasioKeuntungan: Number(value) })
}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pencarian Investor"
data={dummyListPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
}
value={data.pencarianInvestor}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Periode Deviden"
data={dummyPeriodeDeviden.map((item) => ({
label: item.name,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, periodeDeviden: value as any })
}
value={data.periodeDeviden}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden"
data={dummyPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any })
}
value={data.pembagianDeviden}
/>
<Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing height={50} />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,9 @@
import { TextCustom, ViewWrapper } from "@/components";
export default function InvestmentDetail() {
return (
<ViewWrapper>
<TextCustom>Investment Detail</TextCustom>
</ViewWrapper>
)
}

View File

@@ -0,0 +1,16 @@
import { ViewWrapper } from "@/components";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
export default function InvestmentListOfDocument() {
return (
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<Investment_BoxDetailDocument
key={index}
title={`Judul Dokumen ${index + 1}`}
href={`/investment/${index + 1}/dokumen/file`}
/>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,133 @@
import {
AlertDefaultSystem,
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail";
import { AntDesign, Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function InvestmentRecapOfDocument() {
const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerBox, setOpenDrawerBox] = useState(false);
return (
<>
<Stack.Screen
options={{
title: "Rekap Dokumen",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton
onPress={() => {
setOpenDrawer(true);
setOpenDrawerBox(false);
}}
/>
),
}}
/>
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<Investment_BoxDetailDocument
key={index}
title={`Judul Dokumen ${index + 1}`}
leftIcon={
<Ionicons
name="ellipsis-horizontal-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
style={{
zIndex: 10,
alignSelf: "flex-end",
}}
onPress={() => setOpenDrawerBox(true)}
/>
}
href={`/investment/${id}/dokumen/file`}
/>
))}
</ViewWrapper>
{/* Drawer On Header */}
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: (
<AntDesign
name="pluscircle"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
label: "Tambah Dokumen",
path: `/investment/${id}/add-document`,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
{/* Drawer On Box */}
<DrawerCustom
isVisible={openDrawerBox}
closeDrawer={() => setOpenDrawerBox(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconEdit />,
label: "Edit Dokumen",
path: `/investment/${id}/edit-document`,
},
{
icon: (
<Ionicons
name="trash-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
label: "Hapus Dokumen",
path: "" as any,
color: MainColor.red,
},
]}
onPressItem={(item) => {
if (item.path === ("" as any)) {
AlertDefaultSystem({
title: "Hapus Dokumen",
message: "Apakah anda yakin ingin menghapus dokumen ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
setOpenDrawerBox(false);
},
});
}
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,197 @@
import {
BaseBox,
ButtonCenteredOnly,
ButtonCustom,
CenterCustom,
InformationBox,
LandscapeFrameUploaded,
SelectCustom,
Spacing,
StackCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import dummyPembagianDeviden from "@/lib/dummy-data/investment/pembagian-deviden";
import dummyListPencarianInvestor from "@/lib/dummy-data/investment/pencarian-investor";
import dummyPeriodeDeviden from "@/lib/dummy-data/investment/periode-deviden";
import { FontAwesome5 } from "@expo/vector-icons";
import { router } from "expo-router";
import { useState } from "react";
export default function InvestmentCreate() {
const [data, setData] = useState({
title: "",
targetDana: 0,
hargaPerLembar: 0,
totalLembar: 0,
rasioKeuntungan: 0,
pencarianInvestor: "",
periodeDeviden: "",
pembagianDeviden: "",
});
// const [coba, setCoba] = useState("");
return (
<ViewWrapper>
<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." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
icon="upload"
onPress={() => router.push("/take-picture/1")}
>
Upload
</ButtonCenteredOnly>
<Spacing />
<InformationBox text="File prospektus wajib untuk diupload, agar calon investor paham dengan prospek investasi yang akan anda jalankan kedepannya." />
<BaseBox>
<CenterCustom>
<FontAwesome5
name="file-pdf"
size={30}
color={MainColor.disabled}
/>
</CenterCustom>
</BaseBox>
<ButtonCenteredOnly
icon="upload"
onPress={() => router.push("/take-picture/1")}
>
Upload File
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
required
placeholder="Judul"
label="Judul"
value={data.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<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
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
required
iconLeft="Rp."
placeholder="0"
label="Harga Per Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, targetDana: Number(value) })
}
value={data.targetDana === 0 ? "" : data.targetDana.toString()}
/>
<TextInputCustom
required
placeholder="0"
label="Total Lembar"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, totalLembar: Number(value) })
}
value={data.totalLembar === 0 ? "" : data.totalLembar.toString()}
/>
<TextInputCustom
required
iconRight="%"
label="Rasio Keuntungan / ROI %"
placeholder="0"
keyboardType="numeric"
onChangeText={(value) =>
setData({ ...data, rasioKeuntungan: Number(value) })
}
value={
data.rasioKeuntungan === 0 ? "" : data.rasioKeuntungan.toString()
}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pencarian Investor"
data={dummyListPencarianInvestor.map((item) => ({
label: item.name + `${" "}hari`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pencarianInvestor: value as any })
}
value={data.pencarianInvestor}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Periode Deviden"
data={dummyPeriodeDeviden.map((item) => ({
label: item.name,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, periodeDeviden: value as any })
}
value={data.periodeDeviden}
/>
<SelectCustom
required
placeholder="Pilih batas waktu"
label="Pilih Pembagian Deviden"
data={dummyPembagianDeviden.map((item) => ({
label: item.name + `${" "}bulan`,
value: item.id,
}))}
onChange={(value) =>
setData({ ...data, pembagianDeviden: value as any })
}
value={data.pembagianDeviden}
/>
<Spacing />
<ButtonCustom onPress={() => router.replace("/investment/portofolio")}>
Simpan
</ButtonCustom>
</StackCustom>
<Spacing height={50} />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,34 @@
import { IconHome, IconStatus } from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
export default function JobTabsLayout() {
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="archive"
options={{
title: "Arsip",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="archive" color={color} />
),
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,16 @@
import { BaseBox, TextCustom, ViewWrapper } from "@/components";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
export default function JobArchive() {
return (
<ViewWrapper hideFooter>
{jobDataDummy.map((e, i) => (
<BaseBox key={i} paddingTop={20} paddingBottom={20}>
<TextCustom align="center" bold truncate size="large">
{e.posisi}
</TextCustom>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,34 @@
import {
AvatarUsernameAndOtherComponent,
BoxWithHeaderSection,
FloatingButton,
SearchInput,
Spacing,
TextCustom,
ViewWrapper
} from "@/components";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
import { router } from "expo-router";
export default function JobBeranda() {
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/job/create")} />
}
headerComponent={<SearchInput placeholder="Cari pekerjaan" />}
>
{jobDataDummy.map((item, index) => (
<BoxWithHeaderSection key={index} onPress={() => router.push(`/job/${item.id}`)}>
<AvatarUsernameAndOtherComponent avatarHref={`/profile/${item.id}`} />
<Spacing />
<TextCustom truncate={2} align="center" bold size="large">
{item.posisi}
</TextCustom>
<Spacing />
</BoxWithHeaderSection>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,50 @@
import {
BaseBox,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { masterStatus } from "@/lib/dummy-data/_master/status";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
import { useState } from "react";
export default function JobStatus() {
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={masterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{jobDataDummy.map((e, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/job/${e.id}/${activeCategory}/detail`}
// onPress={() => console.log("pressed")}
>
<TextCustom align="center" bold truncate size="large">
{e.posisi} {activeCategory?.toUpperCase()}
</TextCustom>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,65 @@
import {
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
} from "@/components";
import { IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import Job_ButtonStatusSection from "@/screens/Job/ButtonStatusSection";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function JobDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const jobDetail = jobDataDummy.find((e) => e.id === Number(id));
const handlePress = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
setOpenDrawer(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawer(true)} />
) : null,
}}
/>
<ViewWrapper>
<Job_BoxDetailSection data={jobDetail} />
<Job_ButtonStatusSection status={status as string} />
<Spacing />
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconEdit />,
label: "Edit",
path: `/job/${id}/edit`,
},
]}
columns={4}
onPressItem={handlePress as any}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,67 @@
import {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
export default function JobEdit() {
const buttonSubmit = () => {
return (
<>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
<Spacing />
</>
);
};
return (
<ViewWrapper>
<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." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
{buttonSubmit()}
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,79 @@
import {
ButtonCustom,
Spacing,
ViewWrapper
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
import { jobDataDummy } from "@/screens/Job/listDataDummy";
import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard";
import { useLocalSearchParams } from "expo-router";
import { Alert, Linking } from "react-native";
export default function JobDetail() {
const { id } = useLocalSearchParams();
const jobDetail = jobDataDummy.find((e) => e.id === Number(id));
const OpenLinkButton = () => {
const jobUrl =
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657";
const openInBrowser = async () => {
const supported = await Linking.canOpenURL(jobUrl);
if (supported) {
await Linking.openURL(jobUrl);
} else {
Alert.alert("Gagal membuka link", "Browser tidak tersedia.");
}
};
return (
<ButtonCustom
iconLeft={
<Ionicons name="globe" size={ICON_SIZE_SMALL} color="white" />
}
onPress={openInBrowser}
backgroundColor="green"
textColor="white"
>
Buka Lowongan di Browser
</ButtonCustom>
);
};
const CopyLinkButton = () => {
const jobUrl =
"https://stg-hipmi.wibudev.com/job-vacancy/cm6ijt9w8005zucv4twsct657";
const copyToClipboard = async () => {
await Clipboard.setStringAsync(jobUrl);
Alert.alert(
"Link disalin",
"Tautan lowongan telah disalin ke clipboard."
);
};
return (
<ButtonCustom
iconLeft={<Ionicons name="copy" size={ICON_SIZE_SMALL} color="white" />}
onPress={copyToClipboard}
backgroundColor={MainColor.orange}
textColor="white"
>
Salin Link
</ButtonCustom>
);
};
return (
<ViewWrapper>
<Job_BoxDetailSection data={jobDetail}/>
<OpenLinkButton />
<Spacing />
<CopyLinkButton />
</ViewWrapper>
);
}

View File

@@ -0,0 +1,73 @@
import {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
export default function JobCreate() {
const buttonSubmit = () => {
return (
<>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/job/(tabs)/status")
}
>
Simpan
</ButtonCustom>
<Spacing />
</>
);
};
return (
<ViewWrapper>
<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." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
{buttonSubmit()}
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -11,18 +11,18 @@ import { useState } from "react";
import { View } from "react-native";
const categories = [
{ id: 1, label: "Semua" },
{ id: 2, label: "Event" },
{ id: 3, label: "Job" },
{ id: 4, label: "Voting" },
{ id: 5, label: "Donasi" },
{ id: 6, label: "Investasi" },
{ id: 7, label: "Forum" },
{ id: 8, label: "Collaboration" },
{ value: "all", label: "Semua" },
{ value: "event", label: "Event" },
{ value: "job", label: "Job" },
{ value: "voting", label: "Voting" },
{ value: "donasi", label: "Donasi" },
{ value: "investasi", label: "Investasi" },
{ value: "forum", label: "Forum" },
{ value: "collaboration", label: "Collaboration" },
];
const selectedCategory = (id: number) => {
const category = categories.find((c) => c.id === id);
const selectedCategory = (value: string) => {
const category = categories.find((c) => c.value === value);
return category?.label;
};
@@ -31,7 +31,7 @@ const BoxNotification = ({
activeCategory,
}: {
index: number;
activeCategory: number | null;
activeCategory: string | null;
}) => {
return (
<>
@@ -39,13 +39,13 @@ const BoxNotification = ({
onPress={() =>
console.log(
"Notification >",
selectedCategory(activeCategory as number)
selectedCategory(activeCategory as string)
)
}
>
<StackCustom>
<TextCustom bold>
# {selectedCategory(activeCategory as number)}
# {selectedCategory(activeCategory as string)}
</TextCustom>
<View
@@ -81,38 +81,31 @@ const BoxNotification = ({
};
export default function Notifications() {
const [activeCategory, setActiveCategory] = useState<number | null>(1);
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const handlePress = (item: any) => {
setActiveCategory(item.id);
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
return (
<ViewWrapper
headerComponent={
<ScrollableCustom
data={categories}
data={categories.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
activeId={activeCategory as string}
/>
}
>
{Array.from({ length: 20 }).map((e, i) => (
<View key={i}>
<BoxNotification index={i} activeCategory={activeCategory} />
<BoxNotification index={i} activeCategory={activeCategory as any} />
</View>
))}
{/* Konten utama di sini */}
{/* <View style={{ flex: 1 }}>
<Text style={{ color: "white" }}>
{activeCategory
? `Kategori Aktif: ${
categories.find((c) => c.id === activeCategory)?.label
}`
: "Pilih kategori"}
</Text>
</View> */}
</ViewWrapper>
);
}

View File

@@ -1,4 +1,4 @@
import { DrawerCustom } from "@/components";
import { AlertCustom, DrawerCustom } from "@/components";
import LeftButtonCustom from "@/components/Button/BackButton";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { MainColor } from "@/constants/color-palet";
@@ -7,13 +7,14 @@ import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
import PorfofolioSection from "@/screens/Portofolio/PorfofolioSection";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { Stack, useLocalSearchParams } from "expo-router";
import { Stack, useLocalSearchParams, router } from "expo-router";
import { useState } from "react";
import { TouchableOpacity } from "react-native";
export default function Portofolio() {
const { id } = useLocalSearchParams();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [deleteAlert, setDeleteAlert] = useState(false);
const openDrawer = () => {
setIsDrawerOpen(true);
@@ -42,7 +43,7 @@ export default function Portofolio() {
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
<PorfofolioSection />
<PorfofolioSection setShowDeleteAlert={setDeleteAlert} />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}
@@ -56,6 +57,22 @@ export default function Portofolio() {
setIsDrawerOpen={setIsDrawerOpen}
/>
</DrawerCustom>
{/* Alert Delete */}
<AlertCustom
isVisible={deleteAlert}
onLeftPress={() => setDeleteAlert(false)}
onRightPress={() => {
setDeleteAlert(false);
console.log("Hapus portofolio");
router.back();
}}
title="Hapus Portofolio"
message="Apakah Anda yakin ingin menghapus portofolio ini?"
textLeft="Batal"
textRight="Hapus"
colorRight={MainColor.red}
/>
</>
);
}

View File

@@ -103,14 +103,7 @@ export default function CreateProfile() {
required
onChange={(value) => setData({ ...(data as any), gender: value })}
/>
<TextInputCustom
required
label="Alamat"
placeholder="Masukkan alamat"
value={data.address}
onChangeText={(text) => setData({ ...data, address: text })}
/>
{/* <Spacing /> */}
<Spacing />
</StackCustom>
</ViewWrapper>
);

View File

@@ -22,15 +22,8 @@ export default function ProfileEdit() {
});
const options = [
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Angular", value: "angular" },
{ label: "Svelte", value: "svelte" },
{ label: "Next.js", value: "nextjs" },
{ label: "Nuxt.js", value: "nuxtjs" },
{ label: "Remix", value: "remix" },
{ label: "Sapper", value: "sapper" },
{ label: "SvelteKit", value: "sveltekit" },
{ label: "Laki-laki", value: "laki-laki" },
{ label: "Perempuan", value: "perempuan" },
];
const handleSave = () => {
@@ -59,16 +52,6 @@ export default function ProfileEdit() {
}
>
<StackCustom gap={"xs"}>
<SelectCustom
label="Framework"
placeholder="Pilih framework favoritmu"
data={options}
value={data.selectedValue}
onChange={(value) => {
setData({ ...(data as any), selectedValue: value });
}}
/>
<TextInputCustom
label="Nama"
placeholder="Nama"
@@ -96,6 +79,16 @@ export default function ProfileEdit() {
}}
required
/>
<SelectCustom
required
label="Jenis Kelamin"
placeholder="Pilih jenis kelamin"
data={options}
value={data.selectedValue}
onChange={(value) => {
setData({ ...(data as any), selectedValue: value });
}}
/>
</StackCustom>
</ViewWrapper>
);

View File

@@ -4,8 +4,8 @@ import LeftButtonCustom from "@/components/Button/BackButton";
import DrawerCustom from "@/components/Drawer/DrawerCustom";
import { MainColor } from "@/constants/color-palet";
import { drawerItemsProfile } from "@/screens/Profile/ListPage";
import Profile_MenuDrawerSection from "@/screens/Profile/MenuDrawerSection";
import ProfilSection from "@/screens/Profile/ProfilSection";
import Profile_MenuDrawerSection from "@/screens/Profile/menuDrawerSection";
import ProfileSection from "@/screens/Profile/ProfileSection";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
@@ -52,7 +52,7 @@ export default function Profile() {
headerTitleStyle: GStyles.headerTitleStyle,
}}
/>
<ProfilSection />
<ProfileSection />
</ViewWrapper>
{/* Drawer Komponen Eksternal */}

View File

@@ -0,0 +1,43 @@
import {
IconContribution,
IconHistory,
IconHome,
IconStatus,
} from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Tabs } from "expo-router";
export default function VotingTabsLayout() {
return (
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen
name="status"
options={{
title: "Status",
tabBarIcon: ({ color }) => <IconStatus color={color} />,
}}
/>
<Tabs.Screen
name="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,17 @@
import {
ViewWrapper
} from "@/components";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
export default function VotingContribution() {
return (
<ViewWrapper hideFooter>
{Array.from({ length: 5 }).map((_, index) => (
<Voting_BoxPublishSection
key={index}
href={`/voting/${index}/contribution`}
/>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,37 @@
import { ViewWrapper } from "@/components";
import TabsTwoHeaderCustom from "@/components/_ShareComponent/TabsTwoHeaderCustom";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { useState } from "react";
export default function VotingHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
return (
<ViewWrapper
hideFooter
headerComponent={
<TabsTwoHeaderCustom
leftValue="all"
rightValue="main"
leftText="Semua Riwayat"
rightText="Riwayat Saya"
activeCategory={activeCategory}
handlePress={handlePress}
/>
}
>
{Array.from({ length: 10 }).map((_, index) => (
<Voting_BoxPublishSection
key={index}
id={activeCategory as any}
href={`/voting/${index}/history`}
/>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,23 @@
import {
FloatingButton,
SearchInput,
ViewWrapper
} from "@/components";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { router } from "expo-router";
export default function VotingBeranda() {
return (
<ViewWrapper
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/voting/create")} />
}
headerComponent={<SearchInput placeholder="Cari voting" />}
>
{Array.from({ length: 5 }).map((_, index) => (
<Voting_BoxPublishSection key={index} href={`/voting/${index}`} />
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,60 @@
import {
BadgeCustom,
BaseBox,
ScrollableCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { masterStatus } from "@/lib/dummy-data/_master/status";
import dayjs from "dayjs";
import { useState } from "react";
export default function VotingStatus() {
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={masterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={scrollComponent} hideFooter>
{Array.from({ length: 10 }).map((_, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
href={`/voting/${i}/${activeCategory}/detail`}
>
<StackCustom>
<TextCustom align="center" bold truncate size="large">
Lorem ipsum dolor sit {activeCategory}
</TextCustom>
<BadgeCustom
style={{ width: "70%", alignSelf: "center" }}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
</StackCustom>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,108 @@
import {
AlertDefaultSystem,
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
} from "@/components";
import { IconArchive, IconContribution, IconEdit } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { Voting_BoxDetailSection } from "@/screens/Voting/BoxDetailSection";
import Voting_ButtonStatusSection from "@/screens/Voting/ButtonStatusSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function VotingDetailStatus() {
const { id, status } = useLocalSearchParams();
const [openDrawerDraft, setOpenDrawerDraft] = useState(false);
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const handlePressDraft = (item: IMenuDrawerItem) => {
console.log("PATH >> ", item.path);
router.navigate(item.path as any);
setOpenDrawerDraft(false);
};
const handlePressPublish = (item: IMenuDrawerItem) => {
if (item.path === "") {
AlertDefaultSystem({
title: "Update Arsip",
message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
}
router.navigate(item.path as any);
setOpenDrawerPublish(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail ${status}`,
headerLeft: () => <BackButton />,
headerRight: () =>
status === "draft" ? (
<DotButton onPress={() => setOpenDrawerDraft(true)} />
) : status === "publish" ? (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
) : null,
}}
/>
<ViewWrapper>
<Voting_BoxDetailSection />
<Voting_ButtonStatusSection status={status as string} />
<Spacing />
</ViewWrapper>
{/* ========= Draft Drawer ========= */}
<DrawerCustom
isVisible={openDrawerDraft}
closeDrawer={() => setOpenDrawerDraft(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconEdit />,
label: "Edit",
path: `/voting/${id}/edit`,
},
]}
columns={4}
onPressItem={handlePressDraft as any}
/>
</DrawerCustom>
{/* ========= Publish Drawer ========= */}
<DrawerCustom
isVisible={openDrawerPublish}
closeDrawer={() => setOpenDrawerPublish(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
{
icon: <IconArchive />,
label: "Update Arsip",
path: "" as any,
},
]}
onPressItem={handlePressPublish as any}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,65 @@
import {
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
} from "@/components";
import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import { Voting_BoxDetailContributionSection } from "@/screens/Voting/BoxDetailContribution";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function VotingDetailContribution() {
const { id } = useLocalSearchParams();
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const handlePressPublish = (item: IMenuDrawerItem) => {
router.navigate(item.path as any);
setOpenDrawerPublish(false);
};
return (
<>
<Stack.Screen
options={{
title: "Detail Kontribusi",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
),
}}
/>
<ViewWrapper>
<Voting_BoxDetailContributionSection
headerAvatar={<AvatarUsernameAndOtherComponent />}
/>
<Voting_BoxDetailHasilVotingSection />
<Spacing />
</ViewWrapper>
{/* ========= Publish Drawer ========= */}
<DrawerCustom
isVisible={openDrawerPublish}
closeDrawer={() => setOpenDrawerPublish(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
]}
onPressItem={handlePressPublish as any}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,78 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { TouchableOpacity } from "react-native";
export default function VotingEdit() {
const buttonSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.back()
}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
/>
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
<Grid>
<Grid.Col span={10}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
/>
</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 />
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,64 @@
import {
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
MenuDrawerDynamicGrid,
Spacing,
ViewWrapper,
} from "@/components";
import { IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailHistorySection } from "@/screens/Voting/BoxDetailHistorySection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
export default function VotingDetailHistory() {
const { id } = useLocalSearchParams();
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const handlePressPublish = (item: IMenuDrawerItem) => {
router.navigate(item.path as any);
setOpenDrawerPublish(false);
};
return (
<>
<Stack.Screen
options={{
title: "Riwayat Voting",
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
),
}}
/>
<ViewWrapper>
<Voting_BoxDetailHistorySection
headerAvatar={<AvatarUsernameAndOtherComponent />}
/>
<Voting_BoxDetailHasilVotingSection />
<Spacing />
</ViewWrapper>
{/* ========= Publish Drawer ========= */}
<DrawerCustom
isVisible={openDrawerPublish}
closeDrawer={() => setOpenDrawerPublish(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
]}
onPressItem={handlePressPublish as any}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,87 @@
import {
AlertDefaultSystem,
AvatarUsernameAndOtherComponent,
BackButton,
DotButton,
DrawerCustom,
InformationBox,
MenuDrawerDynamicGrid,
StackCustom,
ViewWrapper,
} from "@/components";
import { IconArchive, IconContribution } from "@/components/_Icon";
import { IMenuDrawerItem } from "@/components/_Interface/types";
import Voting_BoxDetailHasilVotingSection from "@/screens/Voting/BoxDetailHasilVotingSection";
import { Voting_BoxDetailPublishSection } from "@/screens/Voting/BoxDetailPublishSection";
import { router, Stack, useLocalSearchParams } from "expo-router";
import React, { useState } from "react";
export default function VotingDetail() {
const { id } = useLocalSearchParams();
const [openDrawerPublish, setOpenDrawerPublish] = useState(false);
const handlePressPublish = (item: IMenuDrawerItem) => {
if (item.path === "") {
AlertDefaultSystem({
title: "Update Arsip",
message: "Apakah Anda yakin ingin mengarsipkan voting ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
}
router.navigate(item.path as any);
setOpenDrawerPublish(false);
};
return (
<>
<Stack.Screen
options={{
title: `Detail Voting`,
headerLeft: () => <BackButton />,
headerRight: () => (
<DotButton onPress={() => setOpenDrawerPublish(true)} />
),
}}
/>
<ViewWrapper>
<StackCustom>
<InformationBox text="Untuk sementara voting ini belum di buka. Voting akan dimulai sesuai dengan tanggal awal pemilihan, dan akan ditutup sesuai dengan tanggal akhir pemilihan." />
<Voting_BoxDetailPublishSection
headerAvatar={<AvatarUsernameAndOtherComponent />}
/>
<Voting_BoxDetailHasilVotingSection />
</StackCustom>
</ViewWrapper>
{/* ========= Publish Drawer ========= */}
<DrawerCustom
isVisible={openDrawerPublish}
closeDrawer={() => setOpenDrawerPublish(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
icon: <IconContribution />,
label: "Daftar Kontributor",
path: `/voting/${id}/list-of-contributor`,
},
{
icon: <IconArchive />,
label: "Update Arsip",
path: "" as any,
},
]}
onPressItem={handlePressPublish as any}
/>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,26 @@
import {
AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox,
ViewWrapper,
} from "@/components";
export default function Voting_ListOfContributor() {
return (
<ViewWrapper>
{Array.from({ length: 10 }).map((_, index) => (
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
<AvatarUsernameAndOtherComponent
rightComponent={
<BadgeCustom
style={{alignSelf: "flex-end" }}
>
Pilihan {index + 1}
</BadgeCustom>
}
/>
</BaseBox>
))}
</ViewWrapper>
);
}

View File

@@ -0,0 +1,78 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { TouchableOpacity } from "react-native";
export default function VotingCreate() {
const buttonSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/voting/(tabs)/status")
}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
/>
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
<Grid>
<Grid.Col span={10}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
/>
</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 />
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -0,0 +1,258 @@
// File: src/screens/EventDetailScreen.tsx
import LeftButtonCustom from "@/components/Button/BackButton";
import { MainColor } from "@/constants/color-palet";
import React, { useState, useEffect } from "react";
import {
FlatList,
View,
Text,
Image,
StyleSheet,
ActivityIndicator,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
// === TYPES ===
type Participant = {
id: number;
name: string;
avatar: string;
};
type EventDetail = {
id: number;
title: string;
description: string;
date: string;
location: string;
organizer: string;
};
// === KOMPONEN UTAMA ===
const EventDetailScreen: React.FC = () => {
const [participants, setParticipants] = useState<Participant[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [loadingMore, setLoadingMore] = useState<boolean>(false);
// Data event
const event: EventDetail = {
id: 1,
title: "Workshop React Native & Expo",
description:
"Pelatihan intensif pengembangan aplikasi mobile menggunakan React Native, Expo, dan TypeScript. Cocok untuk developer tingkat menengah.",
date: "Sabtu, 5 April 2025 | 09:00 - 16:00",
location: "Gedung Teknologi, Jakarta Selatan",
organizer: "DevCommunity Indonesia",
};
// Simulasi API: generate data dummy
const generateParticipants = (
startId: number,
count: number
): Participant[] => {
return Array.from({ length: count }, (_, i) => {
const id = startId + i;
return {
id,
name: `Peserta ${id}`,
avatar: `https://i.pravatar.cc/150?img=${(id % 70) + 1}`, // 70 gambar unik
};
});
};
// Load data awal
useEffect(() => {
const loadInitial = () => {
setTimeout(() => {
const initialData = generateParticipants(1, 20); // 20 peserta pertama
setParticipants(initialData);
setLoading(false);
}, 800);
};
loadInitial();
}, []);
// Load lebih banyak peserta saat scroll ke bawah
const loadMore = () => {
if (loadingMore || participants.length >= 200) return; // Batas 200 peserta
setLoadingMore(true);
setTimeout(() => {
const nextId = participants.length + 1;
const newData = generateParticipants(nextId, 10); // Tambah 10 peserta
setParticipants((prev) => [...prev, ...newData]);
setLoadingMore(false);
}, 1000);
};
// Render footer: loading indicator
const renderFooter = () => {
if (!loadingMore) return null;
return (
<View style={styles.footer}>
<ActivityIndicator size="small" color="#007AFF" />
<Text style={styles.loadingText}> Memuat peserta berikutnya...</Text>
</View>
);
};
// Render header: detail event + info jumlah peserta
const renderHeader = () => (
<>
<View style={styles.headerContainer}>
<LeftButtonCustom path={"/"} />
<Text style={styles.title}>{event.title}</Text>
<Text style={styles.info}>📅 {event.date}</Text>
<Text style={styles.info}>📍 {event.location}</Text>
<Text style={styles.info}>👤 {event.organizer}</Text>
<Text style={styles.description}>{event.description}</Text>
<View style={styles.divider} />
<Text style={styles.sectionTitle}>
Daftar Peserta ({participants.length})
</Text>
</View>
{/* Sub-header tambahan jika perlu */}
{participants.length === 0 ? (
<Text style={styles.empty}>Belum ada peserta yang terdaftar.</Text>
) : null}
</>
);
// Loading awal
if (loading) {
return (
<SafeAreaView style={styles.container}>
<ActivityIndicator size="large" color="#007AFF" style={styles.loader} />
</SafeAreaView>
);
}
return (
<>
<FlatList
data={participants}
keyExtractor={(item) => item.id.toString()}
ListHeaderComponent={renderHeader}
ListFooterComponent={renderFooter}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
renderItem={({ item }) => (
<View style={styles.participantItem}>
<Image source={{ uri: item.avatar }} style={styles.avatar} />
<Text style={styles.participantName}>{item.name}</Text>
</View>
)}
initialNumToRender={10}
maxToRenderPerBatch={5}
windowSize={7}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<Text style={styles.empty}>Tidak ada peserta.</Text>
}
/>
<SafeAreaView
edges={["bottom"]}
style={{ backgroundColor: MainColor.darkblue }}
/>
</>
);
};
export default EventDetailScreen;
// === STYLES ===
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f8f9fa",
},
loader: {
marginTop: 20,
},
headerContainer: {
padding: 16,
backgroundColor: "#ffffff",
borderBottomWidth: 1,
borderBottomColor: "#e0e0e0",
},
title: {
fontSize: 24,
fontWeight: "bold",
color: "#1d1d1d",
marginBottom: 8,
},
info: {
fontSize: 16,
color: "#444",
marginBottom: 4,
},
description: {
fontSize: 14,
color: "#666",
lineHeight: 20,
marginTop: 12,
},
divider: {
height: 1,
backgroundColor: "#e0e0e0",
marginVertical: 16,
},
sectionTitle: {
fontSize: 18,
fontWeight: "600",
color: "#1d1d1d",
marginBottom: 12,
},
participantItem: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: "#fff",
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 12,
},
participantName: {
fontSize: 16,
color: "#333",
},
footer: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 16,
backgroundColor: "#f8f9fa",
},
loadingText: {
color: "#555",
fontSize: 14,
},
empty: {
textAlign: "center",
color: "#999",
fontStyle: "italic",
padding: 16,
backgroundColor: "#fff",
fontSize: 14,
},
subHeader: {
paddingHorizontal: 16,
paddingVertical: 8,
backgroundColor: "#f1f1f1",
fontSize: 14,
color: "#666",
fontStyle: "italic",
},
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from "react";
import {
View,
@@ -8,7 +9,11 @@ import {
ScrollView,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { router, Stack } from "expo-router";
import EventDetailScreen from "./double-scroll";
import LeftButtonCustom from "@/components/Button/BackButton";
import CustomUploadButton from "./upload-button";
import { SafeAreaView } from "react-native-safe-area-context";
const { width } = Dimensions.get("window");
@@ -16,7 +21,9 @@ const { width } = Dimensions.get("window");
const HomeScreen = () => (
<View style={styles.screen}>
<Text style={styles.screenTitle}>Selamat Datang!</Text>
<Text style={styles.screenText}>Ini adalah halaman utama aplikasi Anda</Text>
<Text style={styles.screenText}>
Ini adalah halaman utama aplikasi Anda
</Text>
</View>
);
const SearchScreen = () => (
@@ -65,9 +72,7 @@ const CustomTab = ({ icon, label, isActive, onPress }: any) => (
// Main Custom Tab Navigator
const CustomTabNavigator = () => {
const [activeTab, setActiveTab] = React.useState(
'home'
);
const [activeTab, setActiveTab] = React.useState("home");
const [showHome, setShowHome] = React.useState(true);
const tabs = [
@@ -97,13 +102,12 @@ const CustomTabNavigator = () => {
},
];
// Function untuk handle tab press
const handleTabPress = (tabId: string) => {
setActiveTab(tabId);
setShowHome(false); // Hide home when any tab is pressed
};
// Determine which component to show
const getActiveComponent = () => {
if (showHome || activeTab === "home") {
@@ -111,38 +115,70 @@ const CustomTabNavigator = () => {
}
// const selectedTab = tabs.find((tab) => tab.id === activeTab);
// return selectedTab ? selectedTab.component : HomeScreen;
return HomeScreen
return HomeScreen;
};
const ActiveComponent = getActiveComponent();
return (
<View style={styles.container}>
{/* Content Area */}
<ScrollView>
<View style={styles.content}>
<ActiveComponent />
</View>
</ScrollView>
const handleImageUpload = (file: any) => {
console.log("Gambar dipilih:", file);
// Upload ke server
};
{/* Custom Tab Bar */}
<View style={styles.tabBar}>
<View style={styles.tabContainer}>
{tabs.map((e) => (
<CustomTab
key={e.id}
icon={activeTab === e.id ? e.activeIcon : e.icon}
label={e.label}
isActive={activeTab === e.id && !showHome}
onPress={() => {
handleTabPress(e.id);
router.push(e.path as any);
}}
/>
))}
</View>
</View>
</View>
const handlePdfOrPngUpload = (file: any) => {
console.log("PDF atau PNG dipilih:", file);
};
return (
<>
<SafeAreaView edges={["bottom"]} style={styles.container}>
<Stack.Screen
options={{
title: "Custom Tab Navigator",
}}
/>
<EventDetailScreen />
<CustomUploadButton
allowedExtensions={["jpeg", "png"]}
buttonTitle="Unggah Gambar (JPEG/PNG)"
onFileSelected={handleImageUpload}
/>
{/* Hanya PDF atau PNG */}
<CustomUploadButton
allowedExtensions={["pdf"]}
buttonTitle="Unggah PDF atau PNG"
onFileSelected={handlePdfOrPngUpload}
/>
</SafeAreaView>
</>
// <View style={styles.container}>
// {/* Content Area */}
// <ScrollView>
// <View style={styles.content}>
// <ActiveComponent />
// </View>
// </ScrollView>
// {/* Custom Tab Bar */}
// <View style={styles.tabBar}>
// <View style={styles.tabContainer}>
// {tabs.map((e) => (
// <CustomTab
// key={e.id}
// icon={activeTab === e.id ? e.activeIcon : e.icon}
// label={e.label}
// isActive={activeTab === e.id && !showHome}
// onPress={() => {
// handleTabPress(e.id);
// router.push(e.path as any);
// }}
// />
// ))}
// </View>
// </View>
// </View>
);
};

View File

@@ -0,0 +1,99 @@
// components/CustomUploadButton.tsx
import React from 'react';
import { Button, Alert, View, StyleSheet } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import { isValidFileType, getMimeType } from '../../../utils/fileValidation';
interface UploadButtonProps {
allowedExtensions: string[];
buttonTitle?: string;
onFileSelected?: (file: {
uri: string;
name: string;
size: number | null;
mimeType: string;
}) => void;
}
const CustomUploadButton: React.FC<UploadButtonProps> = ({
allowedExtensions,
buttonTitle = 'Pilih File',
onFileSelected,
}) => {
const handlePickFile = async () => {
try {
// Coba filter dengan MIME type jika memungkinkan
const typeFilter = getMimeTypeFilter(allowedExtensions);
const result = await DocumentPicker.getDocumentAsync({
type: typeFilter, // Ini membantu memfilter di UI pemilih
copyToCacheDirectory: true,
});
if (result.canceled) {
Alert.alert('Dibatalkan', 'Tidak ada file yang dipilih.');
return;
}
const file = result.assets[0];
const { uri, name, size } = file;
// Validasi ekstensi secara manual (cadangan jika MIME tidak akurat)
if (!isValidFileType(name, allowedExtensions)) {
Alert.alert(
'Format Tidak Didukung',
`Hanya file dengan ekstensi berikut yang diperbolehkan: ${allowedExtensions.join(', ')}`
);
return;
}
const mimeType = getMimeType(name);
// Kirim data file ke komponen induk
if (onFileSelected) {
onFileSelected({ uri, name, size: size || null, mimeType });
}
Alert.alert('Berhasil', `File ${name} berhasil dipilih!`);
} catch (error) {
console.error('Error picking file:', error);
Alert.alert('Error', 'Terjadi kesalahan saat memilih file.');
}
};
return (
<View style={styles.container}>
<Button title={buttonTitle} onPress={handlePickFile} />
</View>
);
};
// Fungsi bantu untuk menghasilkan MIME type filter dari ekstensi
const getMimeTypeFilter = (extensions: string[]): string => {
const mimeTypes: string[] = [];
extensions.forEach((ext) => {
switch (ext.toLowerCase()) {
case 'jpg':
case 'jpeg':
if (!mimeTypes.includes('image/jpeg')) mimeTypes.push('image/jpeg');
break;
case 'png':
if (!mimeTypes.includes('image/png')) mimeTypes.push('image/png');
break;
case 'pdf':
if (!mimeTypes.includes('application/pdf')) mimeTypes.push('application/pdf');
break;
default:
mimeTypes.push('*/*'); // fallback
}
});
return mimeTypes.length > 0 ? mimeTypes.join(',') : '*/*';
};
const styles = StyleSheet.create({
container: {
marginVertical: 10,
},
});
export default CustomUploadButton;

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

View File

@@ -5,17 +5,22 @@
"name": "hipmi-mobile",
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-native-community/datetimepicker": "8.4.1",
"@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/drawer": "^7.5.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.21",
"@types/lodash": "^4.17.20",
"@types/react-native-vector-icons": "^6.4.18",
"dayjs": "^1.11.13",
"expo": "53.0.17",
"expo-blur": "~14.1.5",
"expo-camera": "~16.1.10",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.7",
"expo-document-picker": "~13.1.6",
"expo-file-system": "~18.1.11",
"expo-font": "~13.3.2",
"expo-haptics": "~14.1.4",
"expo-image": "~2.3.2",
@@ -27,6 +32,7 @@
"expo-symbols": "~0.4.5",
"expo-system-ui": "~5.0.10",
"expo-web-browser": "~14.2.0",
"lodash": "^4.17.21",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.5",
@@ -34,6 +40,7 @@
"react-native-international-phone-number": "^0.9.3",
"react-native-maps": "1.20.1",
"react-native-otp-entry": "^1.8.5",
"react-native-pager-view": "6.7.1",
"react-native-paper": "^5.14.5",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.4.0",
@@ -372,6 +379,8 @@
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@react-native-community/datetimepicker": ["@react-native-community/datetimepicker@8.4.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["expo", "react-native-windows"] }, "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.5", "", {}, "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.5" } }, "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w=="],
@@ -444,6 +453,8 @@
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
"@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
"@types/react": ["@types/react@19.0.14", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw=="],
@@ -824,8 +835,12 @@
"expo-camera": ["expo-camera@16.1.10", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-qoRJeSwPmMbuu0VfnQTC+q79Kt2SqTWColEImgithL9u0qUQcC55U89IfhZk55Hpt6f1DgKuDzUOG5oY+snSWg=="],
"expo-clipboard": ["expo-clipboard@7.1.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-TCANUGOxouoJXxKBW5ASJl2WlmQLGpuZGemDCL2fO5ZMl57DGTypUmagb0CVUFxDl0yAtFIcESd78UsF9o64aw=="],
"expo-constants": ["expo-constants@17.1.7", "", { "dependencies": { "@expo/config": "~11.0.12", "@expo/env": "~1.0.7" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA=="],
"expo-document-picker": ["expo-document-picker@13.1.6", "", { "peerDependencies": { "expo": "*" } }, "sha512-8FTQPDOkyCvFN/i4xyqzH7ELW4AsB6B3XBZQjn1FEdqpozo6rpNJRr7sWFU/93WrLgA9FJEKpKbyr6XxczK6BA=="],
"expo-file-system": ["expo-file-system@18.1.11", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-HJw/m0nVOKeqeRjPjGdvm+zBi5/NxcdPf8M8P3G2JFvH5Z8vBWqVDic2O58jnT1OFEy0XXzoH9UqFu7cHg9DTQ=="],
"expo-font": ["expo-font@13.3.2", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A=="],
@@ -1148,6 +1163,8 @@
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
@@ -1386,6 +1403,8 @@
"react-native-otp-entry": ["react-native-otp-entry@1.8.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TZNkIuUzZKAAWrC8X/A22ZHJdycLysxUNysrGf0yTmDLRUyf4zLXwVFcDYUcRNe763Hjaf5qvtKGILb6lDGzoA=="],
"react-native-pager-view": ["react-native-pager-view@6.7.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw=="],
"react-native-paper": ["react-native-paper@5.14.5", "", { "dependencies": { "@callstack/react-theme-provider": "^3.0.9", "color": "^3.1.2", "use-latest-callback": "^0.2.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-safe-area-context": "*" } }, "sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ=="],
"react-native-reanimated": ["react-native-reanimated@3.17.5", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw=="],

View File

@@ -0,0 +1,31 @@
import { Alert } from "react-native";
export default function AlertDefaultSystem({
title,
message,
textLeft,
textRight,
onPressLeft,
onPressRight,
}: {
title: string;
message: string;
textLeft: string;
textRight: string;
onPressLeft?: () => void;
onPressRight?: () => void;
}) {
return Alert.alert(title, message, [
{
style: "cancel",
text: textLeft,
onPress: () => onPressLeft?.(),
},
{
style: "default",
text: textRight,
isPreferred: true,
onPress: () => onPressRight?.(),
},
]);
}

View File

@@ -0,0 +1,189 @@
import React from "react";
import {
StyleSheet,
Text,
TextStyle,
View,
ViewProps,
ViewStyle,
} from "react-native";
type BadgeVariant = "filled" | "light" | "outline" | "dot";
type BadgeColor =
| "primary"
| "success"
| "warning"
| "danger"
| "gray"
| "dark";
type BadgeSize = "xs" | "sm" | "md" | "lg";
interface BadgeProps extends ViewProps {
children: React.ReactNode;
variant?: BadgeVariant;
color?: BadgeColor;
size?: BadgeSize;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
radius?: number;
fullWidth?: boolean;
textColor?: string;
}
const BadgeCustom: React.FC<BadgeProps> = ({
children,
variant = "filled",
color = "primary",
size = "md",
leftIcon,
rightIcon,
radius = 50,
fullWidth = false,
textColor = "#fff",
style,
...props
}) => {
const colors = {
primary: "#339AF0",
success: "#40C057",
warning: "#FAB005",
danger: "#FA5252",
gray: "#868E96",
dark: "#212529",
};
const themeColor = colors[color];
// Ganti bagian sizeStyles dan styles.container
const sizeStyles = {
xs: {
fontSize: 10,
paddingHorizontal: 6,
paddingVertical: 2,
height: 18, // Dinaikkan dari 16 → 18 agar teks tidak terpotong
lineHeight: 10, // 👈 Penting: match fontSize agar kontrol vertikal lebih baik
},
sm: {
fontSize: 11,
paddingHorizontal: 8,
paddingVertical: 3,
height: 20,
lineHeight: 11,
},
md: {
fontSize: 12,
paddingHorizontal: 10,
paddingVertical: 4,
height: 24,
lineHeight: 12,
},
lg: {
fontSize: 14,
paddingHorizontal: 12,
paddingVertical: 6,
height: 30,
lineHeight: 14,
},
};
const currentSize = sizeStyles[size];
let variantStyles: ViewStyle & { text: TextStyle } = {
backgroundColor: themeColor,
borderColor: themeColor,
borderWidth: 1,
borderRadius: radius,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
text: { color: textColor, fontWeight: "600" },
};
switch (variant) {
case "light":
variantStyles.backgroundColor = `${themeColor}20`;
variantStyles.text.color = themeColor;
break;
case "outline":
variantStyles.backgroundColor = "transparent";
variantStyles.text.color = themeColor;
break;
case "dot":
variantStyles.backgroundColor = themeColor;
variantStyles.paddingHorizontal = 0;
variantStyles.paddingVertical = 0;
variantStyles.height = currentSize.fontSize * 2;
variantStyles.width = currentSize.fontSize * 2;
variantStyles.borderRadius = currentSize.fontSize;
break;
default:
break;
}
if (variant === "dot") {
return (
<View
style={[variantStyles, fullWidth && styles.fullWidth, style]}
{...props}
/>
);
}
return (
<View
style={[
styles.container,
variantStyles,
currentSize,
{ borderRadius: radius },
fullWidth && styles.fullWidth,
style,
]}
{...props}
>
{leftIcon && <View style={styles.iconContainer}>{leftIcon}</View>}
<Text
style={[
styles.text,
variantStyles.text,
{ fontSize: currentSize.fontSize },
]}
>
{children}
</Text>
{rightIcon && <View style={styles.iconContainer}>{rightIcon}</View>}
</View>
);
};
const styles = StyleSheet.create({
container: {
alignSelf: "flex-start",
flexDirection: "row",
alignItems: "center", // Vertikal center anak-anak (termasuk teks)
justifyContent: "center", // Horizontal center
paddingHorizontal: 10,
paddingVertical: 4,
minWidth: 20,
borderRadius: 6,
// ❌ Jangan gunakan `height` fix di sini — kita override per size
},
text: {
fontWeight: "600",
textAlign: "center",
// ❌ Hapus marginHorizontal jika mengganggu alignment
// marginHorizontal: 2, // Opsional, bisa dihapus atau dikurangi
includeFontPadding: false, // 👈 Ini penting untuk Android!
padding: 0, // Bersihkan padding tambahan dari font
},
iconContainer: {
marginHorizontal: 2, // Lebih kecil dari sebelumnya agar tidak ganggu ukuran kecil
},
fullWidth: {
width: "100%",
alignSelf: "stretch",
justifyContent: "center",
},
});
export default BadgeCustom;

View File

@@ -1,57 +1,79 @@
import { AccentColor } from "@/constants/color-palet";
import { PADDING_EXTRA_SMALL, PADDING_MEDIUM, PADDING_SMALL } from "@/constants/constans-value";
import { StyleProp, TouchableHighlight, View, ViewStyle } from "react-native";
import {
PADDING_MEDIUM,
PADDING_SMALL
} from "@/constants/constans-value";
import { Href, router } from "expo-router";
import {
StyleProp,
TouchableOpacity,
View,
ViewStyle
} from "react-native";
interface BaseBoxProps {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
href?: Href;
onPress?: () => void;
marginBottom?: number;
padding?: number;
paddingTop?: number;
paddingBottom?: number;
paddingInline?: number;
paddingBlock?: number;
backgroundColor?: string;
}
export default function BaseBox({
children,
style,
href,
onPress,
marginBottom = PADDING_MEDIUM,
paddingBlock = PADDING_EXTRA_SMALL,
paddingBlock = PADDING_MEDIUM,
paddingInline = PADDING_SMALL,
paddingTop = PADDING_MEDIUM,
paddingBottom = PADDING_MEDIUM,
backgroundColor = AccentColor.darkblue,
}: BaseBoxProps) {
return (
<>
{onPress ? (
<TouchableHighlight
onPress={onPress}
{onPress || href ? (
<TouchableOpacity
activeOpacity={0.7}
onPress={href ? () => router.navigate(href) : onPress}
style={[
{
backgroundColor: AccentColor.darkblue,
backgroundColor,
borderColor: AccentColor.blue,
borderWidth: 1,
borderRadius: 10,
marginBottom,
paddingBlock,
paddingInline,
paddingTop,
paddingBottom,
},
style,
]}
// activeOpacity={0.7}
>
<View>{children}</View>
</TouchableHighlight>
</TouchableOpacity>
) : (
<View
style={[
{
backgroundColor: AccentColor.darkblue,
backgroundColor,
borderColor: AccentColor.blue,
borderWidth: 1,
borderRadius: 10,
marginBottom,
paddingBlock,
paddingInline,
paddingTop,
paddingBottom,
},
style,
]}

View File

@@ -0,0 +1,20 @@
import { Href } from "expo-router";
import BaseBox from "./BaseBox";
export default function BoxWithHeaderSection({
children,
href,
onPress,
}: {
children: React.ReactNode;
href?: Href;
onPress?: () => void;
}) {
return (
<>
<BaseBox href={href} onPress={onPress} style={{ paddingTop: 5 }}>
{children}
</BaseBox>
</>
);
}

View File

@@ -7,7 +7,7 @@ import BaseBox from "./BaseBox";
export default function InformationBox({ text }: { text: string }) {
return (
<>
<BaseBox>
<BaseBox paddingTop={5} paddingBottom={5}>
<Grid>
<Grid.Col
span={2}

View File

@@ -0,0 +1,13 @@
import { Ionicons } from "@expo/vector-icons";
import { MainColor } from "@/constants/color-palet";
export default function DotButton({ onPress }: { onPress: () => void }) {
return (
<Ionicons
onPress={onPress}
name="ellipsis-vertical"
size={20}
color={MainColor.yellow}
/>
);
}

View File

@@ -0,0 +1,133 @@
import { AccentColor } from "@/constants/color-palet";
import { MaterialIcons } from "@expo/vector-icons"; // Bisa diganti dengan ikon lain
import React, { useContext } from "react";
import { Animated, Text, TouchableOpacity, View } from "react-native";
import { checkboxStyles } from "./checkbox-styles";
// Context untuk Group
interface CheckboxGroupContextType {
value: (string | number)[];
onChange: (value: (string | number)[]) => void;
disabled?: boolean;
}
const CheckboxGroupContext =
React.createContext<CheckboxGroupContextType | null>(null);
// Tipe props
// Tambahkan prop baru: groupValueKey
interface CheckboxProps {
label?: string;
description?: string;
error?: string;
value?: boolean; // controlled value (untuk standalone)
onChange?: (checked: boolean) => void;
disabled?: boolean;
size?: number;
color?: string;
style?: object;
component?: React.ReactNode;
// Prop tambahan untuk Group
valueKey?: string | number; // nilai unik untuk identifikasi di group
}
const CheckboxCustom: React.FC<CheckboxProps> = ({
label,
description,
error,
value: controlledValue,
onChange,
disabled: propDisabled,
size = 20,
color = AccentColor.softblue,
style,
component,
valueKey,
}) => {
// const [uncontrolledChecked, setUncontrolledChecked] = useState(false);
// const isChecked = controlledValue ?? uncontrolledChecked;
// const scaleValue = new Animated.Value(isChecked ? 1 : 0);
const group = useContext(CheckboxGroupContext);
const isInsideGroup = !!group && valueKey !== undefined;
// Jika di dalam group, gunakan logika group
const isChecked = isInsideGroup
? group.value.includes(valueKey!)
: controlledValue ?? false;
const disabled = propDisabled || (isInsideGroup && group.disabled);
const scaleValue = new Animated.Value(isChecked ? 1 : 0);
const toggle = () => {
if (disabled) return;
if (isInsideGroup) {
const newValue = isChecked
? group.value.filter((v) => v !== valueKey)
: [...group.value, valueKey!];
group.onChange(newValue);
} else if (onChange) {
onChange(!controlledValue);
}
};
const styles = checkboxStyles({
size,
color,
disabled: disabled as boolean,
error: !!error,
});
return (
<TouchableOpacity
activeOpacity={disabled ? 1 : 0.7}
onPress={toggle}
style={[styles.container, style]}
disabled={disabled}
>
<View style={styles.innerContainer}>
<View style={[styles.box, isChecked && !disabled && styles.checked]}>
{isChecked && (
<Animated.View
style={{
transform: [
{
scale: scaleValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
},
],
}}
>
<MaterialIcons name="check" size={size * 0.6} color="#fff" />
</Animated.View>
)}
</View>
{component}
{(label || description) && (
<View style={styles.labelWrapper}>
{label ? (
<Text style={styles.label} numberOfLines={1}>
{label}
</Text>
) : null}
{description ? (
<Text style={styles.description} numberOfLines={2}>
{description}
</Text>
) : null}
{error ? <Text style={styles.errorText}>{error}</Text> : null}
</View>
)}
</View>
</TouchableOpacity>
);
};
export default CheckboxCustom;
// Export context agar bisa digunakan
export { CheckboxGroupContext };

View File

@@ -0,0 +1,75 @@
import React, { useState, useMemo } from "react";
import { View, Text, StyleSheet } from "react-native";
import { CheckboxGroupContext } from "./CheckboxCustom";
interface CheckboxGroupProps {
value?: (string | number)[];
onChange?: (values: (string | number)[]) => void;
defaultValue?: (string | number)[];
label?: string;
description?: string;
error?: string;
disabled?: boolean;
children: React.ReactNode;
style?: object;
}
const CheckboxGroup: React.FC<CheckboxGroupProps> = ({
value: controlledValue,
onChange,
defaultValue = [],
label,
description,
error,
disabled = false,
children,
style,
}) => {
const [uncontrolledValue, setUncontrolledValue] =
useState<(string | number)[]>(defaultValue);
const value = controlledValue ?? uncontrolledValue;
const handleChange = onChange ?? setUncontrolledValue;
const contextValue = useMemo(
() => ({ value, onChange: handleChange, disabled }),
[value, handleChange, disabled]
);
return (
<CheckboxGroupContext.Provider value={contextValue}>
<View style={[styles.container, style]}>
{label ? <Text style={styles.label}>{label}</Text> : null}
{description ? (
<Text style={styles.description}>{description}</Text>
) : null}
{children}
{error ? <Text style={styles.errorText}>{error}</Text> : null}
</View>
</CheckboxGroupContext.Provider>
);
};
const styles = StyleSheet.create({
container: {
gap: 8,
},
label: {
fontSize: 16,
fontWeight: "600",
color: "#f8f9fa",
marginBottom: 4,
},
description: {
fontSize: 14,
color: "#ced4da",
marginBottom: 8,
},
errorText: {
color: "#e03131",
fontSize: 14,
marginTop: 4,
},
});
export default CheckboxGroup;

View File

@@ -0,0 +1,61 @@
import { MainColor } from "@/constants/color-palet";
import { StyleSheet } from "react-native";
export const checkboxStyles = (props: {
size: number;
color: string;
disabled: boolean;
error: boolean;
}) =>
StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "flex-start",
// marginBottom: 12,
},
innerContainer: {
flexDirection: "row",
alignItems: "center",
},
box: {
width: props.size,
height: props.size,
borderRadius: 6,
borderWidth: 2,
borderColor: props.error
? "#fff"
: props.disabled
? "#ced4da"
: props.color,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
checked: {
backgroundColor: props.color,
borderColor: props.color,
},
checkIcon: {
color: MainColor.white,
fontWeight: "bold",
fontSize: props.size * 0.6,
},
labelWrapper: {
flex: 1,
},
label: {
fontSize: props.size * 0.6,
color: props.disabled ? MainColor.disabled : MainColor.white,
fontWeight: "500",
},
description: {
fontSize: props.size * 0.5,
color: props.disabled ? MainColor.disabled : MainColor.white,
marginTop: 2,
},
errorText: {
color: MainColor.red,
fontSize: props.size * 0.5,
marginTop: 2,
},
});

View File

@@ -0,0 +1,44 @@
import { MainColor } from "@/constants/color-palet";
import React from "react";
import { StyleSheet, TextInput, View } from "react-native";
interface CircularInputProps {
value: string | number;
onChange?: (value: string) => void;
}
const CircularInput: React.FC<CircularInputProps> = ({ value, onChange }) => {
return (
<View style={styles.circleContainer}>
<TextInput
value={String(value)}
onChangeText={onChange}
style={styles.input}
keyboardType="numeric"
maxLength={2} // Batasan maksimal karakter
/>
</View>
);
};
const styles = StyleSheet.create({
circleContainer: {
width: 60,
height: 60,
borderRadius: 40, // Setiap setengah dari lebar/tinggi
borderWidth: 2,
borderColor: MainColor.yellow, // Warna kuning
justifyContent: "center",
alignItems: "center",
},
input: {
color: MainColor.yellow, // Warna kuning
fontSize: 24,
fontWeight: "bold",
textAlign: "center",
padding: 0,
backgroundColor: "transparent",
},
});
export default CircularInput;

View File

@@ -0,0 +1,205 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// DateTimeInput.tsx
import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import React, { useCallback, useState } from "react";
import { Pressable, StyleProp, Text, View, ViewStyle } from "react-native";
import Grid from "../Grid/GridCustom";
import TextCustom from "../Text/TextCustom";
interface DateTimeInputProps {
// Main
value?: DateTimePickerEvent;
mode?: "date" | "time";
onChange: (selectedDate: DateTimePickerEvent) => void;
maximumDate?: Date;
minimumDate?: Date;
// Main
label?: string;
required?: boolean;
disabled?: boolean;
iconLeft?: React.ReactNode;
style?: StyleProp<ViewStyle>;
borderRadius?: number;
externalError?: string;
internalError?: string;
containerStyle?: StyleProp<ViewStyle>;
}
const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
// Main
value,
mode,
onChange,
maximumDate,
minimumDate,
// Main
label,
required,
disabled,
iconLeft,
style,
borderRadius = 8,
externalError,
internalError,
containerStyle,
}) => {
const [showDate, setShowDate] = useState(false);
const [showTime, setShowTime] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date>(value as any);
const [selectedTime, setSelectedTime] = useState<Date>(value as any);
// Fungsi untuk menggabungkan tanggal dan waktu
const combineDateAndTime = useCallback((date: Date, time: Date): Date => {
const combined = new Date(date);
combined.setHours(
time.getHours(),
time.getMinutes(),
time.getSeconds(),
time.getMilliseconds()
);
return combined;
}, []);
// Handler untuk tanggal
const handleConfirmDate = (event: DateTimePickerEvent, date?: Date) => {
if (event.type === "set" && date) {
setSelectedDate(date);
if (selectedTime) {
const combined = combineDateAndTime(date, selectedTime);
onChange?.(combined as any);
}
}
setShowDate(false);
};
// Handler untuk waktu
const handleConfirmTime = (event: DateTimePickerEvent, time?: Date) => {
if (event.type === "set" && time) {
setSelectedTime(time);
if (selectedDate) {
const combined = combineDateAndTime(selectedDate, time);
onChange?.(combined as any);
}
}
setShowTime(false);
};
const toggleDatePicker = () => {
setShowDate(!showDate);
};
const toggleTimePicker = () => {
setShowTime(!showTime);
};
return (
<>
<View style={[GStyles.inputContainerArea, containerStyle]}>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
style,
{ borderRadius },
externalError || internalError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]}
>
<View style={GStyles.inputIcon}>
<Ionicons
name="calendar-outline"
size={20}
color={MainColor.placeholder}
/>
</View>
<Grid
containerStyle={{
borderRadius: 8,
justifyContent: "center",
alignItems: "center",
}}
>
<Grid.Col span={6} style={{}}>
<Pressable onPress={toggleDatePicker}>
<TextCustom color="gray">
{selectedDate ? (
<TextCustom color="black">
{selectedDate.toLocaleDateString()}
</TextCustom>
) : (
"Pilih tanggal"
)}
</TextCustom>
</Pressable>
</Grid.Col>
<Grid.Col span={1} style={{ alignItems: "center" }}>
<TextCustom color="gray">|</TextCustom>
</Grid.Col>
<Grid.Col span={5} style={{}}>
<Pressable onPress={toggleTimePicker}>
<TextCustom color="gray">
{selectedTime ? (
<TextCustom color="black">
{selectedTime.toLocaleTimeString("id-ID", {
minute: "2-digit",
hour: "2-digit",
})}
</TextCustom>
) : (
"Pilih waktu"
)}
</TextCustom>
</Pressable>
</Grid.Col>
</Grid>
</View>
{externalError ||
(internalError && (
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
))}
</View>
{showDate && (
<DateTimePicker
testID="dateTimePicker"
value={selectedDate || new Date()}
mode="date"
is24Hour={true}
display="default"
onChange={handleConfirmDate}
minimumDate={minimumDate}
maximumDate={maximumDate}
/>
)}
{showTime && (
<DateTimePicker
testID="dateTimePicker"
value={selectedTime || new Date()}
mode="time"
is24Hour={true}
display="default"
onChange={handleConfirmTime}
minimumDate={minimumDate}
maximumDate={maximumDate}
/>
)}
</>
);
};
export default DateTimeInput_Android;

View File

@@ -0,0 +1,161 @@
// DateTimeInput.tsx
import { MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
import { Ionicons } from "@expo/vector-icons";
import DateTimePicker, {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import dayjs from "dayjs";
import React, { useState } from "react";
import {
StyleProp,
Text,
View,
ViewStyle
} from "react-native";
import ClickableCustom from "../Clickable/ClickableCustom";
import TextCustom from "../Text/TextCustom";
interface DateTimeInputProps {
// Main
value?: DateTimePickerEvent;
mode?: "date" | "time";
onChange: (selectedDate: DateTimePickerEvent) => void;
maximumDate?: Date;
minimumDate?: Date;
// Main
label?: string;
required?: boolean;
disabled?: boolean;
iconLeft?: React.ReactNode;
style?: StyleProp<ViewStyle>;
borderRadius?: number;
externalError?: string;
internalError?: string;
containerStyle?: StyleProp<ViewStyle>;
}
const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
// Main
value,
mode,
onChange,
maximumDate,
minimumDate,
// Main
label,
required,
disabled,
iconLeft,
style,
borderRadius = 8,
externalError,
internalError,
containerStyle,
}) => {
const [show, setShow] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(
value as any
);
const handleConfirm = (event: any, date?: Date) => {
if (event.type === "set" && date !== undefined) {
setSelectedDate(date);
onChange(date as any);
}
};
const handlePress = () => {
setShow(!show);
};
return (
<>
<ClickableCustom
activeOpacity={0.8}
style={[GStyles.inputContainerArea, containerStyle]}
onPress={handlePress}
>
{label && (
<Text style={GStyles.inputLabel}>
{label}
{required && <Text style={GStyles.inputRequired}> *</Text>}
</Text>
)}
<View
style={[
style,
{ borderRadius },
externalError || internalError ? GStyles.inputErrorBorder : null,
GStyles.inputContainerInput,
disabled && GStyles.disabledBox,
]}
>
<View style={GStyles.inputIcon}>
<Ionicons
name="calendar-outline"
size={20}
color={MainColor.placeholder}
/>
</View>
<TextCustom color="gray">
{selectedDate ? (
<TextCustom color="black">
{dayjs(selectedDate).format("DD-MM-YYYY HH:mm")}
</TextCustom>
) : (
"Pilih tanggal"
)}
</TextCustom>
</View>
{externalError ||
(internalError && (
<Text style={GStyles.inputErrorMessage}>
{externalError || internalError}
</Text>
))}
</ClickableCustom>
{show && (
<>
<View
style={{
position: "absolute",
zIndex: 15,
backgroundColor: "white",
borderRadius: 8,
padding: 10,
// top: 0,
bottom: 0,
left: 0,
right: 0,
borderColor: "#ccc",
borderWidth: 1,
}}
>
<View style={{ alignItems: "flex-end" }}>
<Ionicons
name="close"
size={20}
color="black"
onPress={() => setShow(false)}
/>
</View>
<DateTimePicker
value={selectedDate || new Date()}
mode={"datetime"}
display="inline"
onChange={handleConfirm}
minimumDate={minimumDate}
maximumDate={maximumDate}
/>
</View>
</>
)}
</>
);
};
export default DateTimeInput_IOS;

View File

@@ -0,0 +1,54 @@
import {
DateTimePickerEvent,
} from "@react-native-community/datetimepicker";
import React from "react";
import { Platform } from "react-native";
import DateTimeInput_Android from "./DataTimeAndroid";
import DateTimeInput_IOS from "./DateTimeIOS";
type Props = {
value?: Date;
onChange?: (date: Date) => void;
label?: string;
required?: boolean;
maximumDate?: Date;
minimumDate?: Date;
};
const DateTimePickerCustom: React.FC<Props> = ({
value,
onChange,
label,
required,
maximumDate,
minimumDate,
}) => {
return (
<>
{Platform.OS === "ios" ? (
<DateTimeInput_IOS
label={label}
onChange={(date: DateTimePickerEvent) => {
onChange?.(date as any);
}}
required={required}
maximumDate={maximumDate}
minimumDate={minimumDate}
/>
) : (
<DateTimeInput_Android
label={label}
onChange={(date: DateTimePickerEvent) => {
onChange?.(date as any);
}}
required={required}
maximumDate={maximumDate}
minimumDate={minimumDate}
/>
)}
</>
);
};
export default DateTimePickerCustom;

View File

@@ -0,0 +1,70 @@
import React, { useState } from "react";
import { Pressable, Text, StyleSheet } from "react-native";
import DateTimePicker, { Event } from "@react-native-community/datetimepicker";
type Props = {
value?: Date;
mode?: "date" | "time" | "datetime";
onChange: (date: Date) => void;
};
const DateTimePickerTry: React.FC<Props> = ({
value = new Date(),
mode = "date",
onChange,
}) => {
const [show, setShow] = useState(false);
const toggleDatePicker = () => {
setShow(!show);
};
const handleConfirm = (event: Event, selectedDate?: Date) => {
if (event.type === "set" && selectedDate !== undefined) {
onChange(selectedDate);
}
setShow(false);
};
return (
<>
<Pressable onPress={toggleDatePicker} style={styles.button}>
<Text style={styles.buttonText}>
{value ? value.toLocaleDateString() : "Pilih tanggal"}
</Text>
</Pressable>
{show && (
<DateTimePicker
// style={styles.button}
textColor="white"
testID="dateTimePicker"
value={value}
mode={mode}
is24Hour={true}
display="default"
onChange={handleConfirm as any}
/>
)}
</>
);
};
const styles = StyleSheet.create({
button: {
paddingVertical: 12,
paddingHorizontal: 20,
backgroundColor: "white",
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
marginVertical: 10,
},
buttonText: {
color: "white",
fontSize: 16,
fontWeight: "bold",
},
});
export default DateTimePickerTry;

View File

@@ -0,0 +1,25 @@
import { AccentColor } from "@/constants/color-palet";
import { View } from "react-native";
export default function Divider({
color = AccentColor.blue,
size = 1,
marginTop= 12,
marginBottom= 12,
}: {
color?: string;
size?: number;
marginTop?: number;
marginBottom?: number;
}) {
return (
<View
style={{
borderTopColor: color,
borderTopWidth: size,
marginTop,
marginBottom,
}}
/>
);
}

View File

@@ -9,20 +9,20 @@ import {
import { AccentColor, MainColor } from "@/constants/color-palet";
import { DRAWER_HEIGHT } from "@/constants/constans-value";
import { SafeAreaView } from "react-native-safe-area-context";
interface DrawerCustomProps {
children?: React.ReactNode;
height?: number;
height?: number | "auto";
isVisible: boolean;
drawerAnim?: Animated.Value;
closeDrawer: () => void;
// openLogoutAlert: () => void;
}
/**
*
* @param drawerAnim
*
* @param drawerAnim
* @example const drawerAnim = useRef(new Animated.Value(DRAWER_HEIGHT)).current; // mulai di luar bawah layar
*/
export default function DrawerCustom({
@@ -34,7 +34,9 @@ export default function DrawerCustom({
}: // openLogoutAlert,
DrawerCustomProps) {
const drawerAnima = useRef(
new Animated.Value(height || DRAWER_HEIGHT)
new Animated.Value(
height === "auto" ? DRAWER_HEIGHT : height || DRAWER_HEIGHT
)
).current;
// Efek untuk handle open/close drawer
useEffect(() => {
@@ -46,7 +48,7 @@ DrawerCustomProps) {
}).start();
} else {
Animated.timing(drawerAnima, {
toValue: height || DRAWER_HEIGHT,
toValue: height === "auto" ? DRAWER_HEIGHT : height || DRAWER_HEIGHT,
duration: 300,
useNativeDriver: true,
}).start();
@@ -99,7 +101,7 @@ DrawerCustomProps) {
style={[
styles.drawer,
{
height: height || DRAWER_HEIGHT,
height: height === "auto" ? "auto" : height || DRAWER_HEIGHT,
transform: [{ translateY: drawerAnima }],
},
]}
@@ -110,34 +112,7 @@ DrawerCustomProps) {
/>
{children}
{/* <TouchableOpacity
style={styles.menuItem}
onPress={() => {
alert("Pilihan 1 diklik");
closeDrawer();
}}
>
<Text>Menu Item 1</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.menuItem}
onPress={() => {
alert("Pilihan 2 diklik");
closeDrawer();
}}
>
<Text>Menu Item 2</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.menuItem}
onPress={() => alert("Logout via Alert bawaan")}
>
<Text style={{ color: "red" }}>Keluar</Text>
</TouchableOpacity> */}
{height === "auto" && <SafeAreaView edges={["bottom"]} />}
</Animated.View>
</>
);

View File

@@ -2,13 +2,26 @@ import { AccentColor, MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_SMALL } from "@/constants/constans-value";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
import { IMenuDrawerItem } from "../_Interface/types";
import { Href } from "expo-router";
const MenuDrawerDynamicGrid = ({ data, columns = 3, onPressItem }: any) => {
type IMenuDrawerItemProps = {
icon: React.ReactNode;
label: string;
path?: Href;
color?: string;
}
interface MenuDrawerDynamicGridProps {
data: IMenuDrawerItemProps[];
columns?: number;
onPressItem?: (item: IMenuDrawerItemProps) => void;
}
const MenuDrawerDynamicGrid = ({ data, columns = 4, onPressItem }: MenuDrawerDynamicGridProps) => {
const numColumns = columns;
return (
<View style={styles.container}>
{data.map((item: IMenuDrawerItem, index: any) => (
{data.map((item: IMenuDrawerItemProps, index: any) => (
<TouchableOpacity
key={index}
style={[styles.itemContainer, { flexBasis: `${100 / numColumns}%` }]}

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