Compare commits

..

35 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
146 changed files with 7484 additions and 347 deletions

View File

@@ -92,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"
@@ -111,7 +315,7 @@ export default function UserLayout() {
name="forum/[id]/forumku"
options={{
title: "Forumku",
headerLeft: () => <BackButton icon={'close'} />,
headerLeft: () => <BackButton icon={"close"} />,
}}
/>
<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,81 +1,43 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { OS_IOS_HEIGHT, OS_ANDROID_HEIGHT } from "@/constants/constans-value";
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";
import { Platform, View } from "react-native";
export default function EventLayout() {
export default function EventTabsLayout() {
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: MainColor.yellow,
tabBarInactiveTintColor: MainColor.white_gray,
tabBarBackground: CustomTabBarBackground,
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
paddingTop: 5,
height: OS_ANDROID_HEIGHT,
},
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>
);
}
function CustomTabBarBackground() {
return (
<View
style={{
flex: 1,
backgroundColor: MainColor.darkblue,
borderTopWidth: 1,
borderTopColor: AccentColor.blue,
}}
/>
);
}

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,30 +1,23 @@
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import FloatingButton from "@/components/Button/FloatingButton";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { GStyles } from "@/styles/global-styles";
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
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/event/create")} />
}
>
<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>
{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

@@ -1,15 +1,15 @@
import {
BoxButtonOnFooter,
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
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";
@@ -23,32 +23,39 @@ export default function EventCreate() {
>(null);
const handlerSubmit = () => {
if (selectedDate) {
console.log("Tanggal yang dipilih:", selectedDate);
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
// Kirim ke API atau proses lanjutan
} else {
console.warn("Tanggal belum dipilih");
}
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.warn("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 = (
<BoxButtonOnFooter>
<ButtonCustom title="Simpan" onPress={handlerSubmit} />
</BoxButtonOnFooter>
<ButtonCustom title="Simpan" onPress={handlerSubmit} />
// <BoxButtonOnFooter>
// </BoxButtonOnFooter>
);
return (
<>
<ViewWrapper footerComponent={buttonSubmit}>
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom
placeholder="Masukkan nama event"
@@ -93,6 +100,8 @@ export default function EventCreate() {
showCount
maxLength={100}
/>
{buttonSubmit}
</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

@@ -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

@@ -11,12 +11,16 @@
"@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",
@@ -28,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",
@@ -35,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",
@@ -447,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=="],
@@ -827,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=="],
@@ -1151,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=="],
@@ -1389,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,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}%` }]}

View File

@@ -0,0 +1,193 @@
import { useTheme } from "@react-navigation/native";
import React from "react";
import { Animated, StyleSheet, Text, View, ViewStyle } from "react-native";
type ProgressColor =
| "primary"
| "success"
| "warning"
| "error"
| "info"
| "dark";
type ProgressSize = "xs" | "sm" | "md" | "lg" | "xl";
interface ProgressProps {
value?: number | null;
color?: ProgressColor;
size?: ProgressSize;
radius?: number;
style?: ViewStyle;
animated?: boolean;
label?: React.ReactNode; // Konten label (bisa string, number, atau elemen)
showLabel?: boolean; // Jika ingin mengontrol visibilitas
}
const getColor = (color: ProgressColor, isDark: boolean) => {
const palette: Record<ProgressColor, string> = {
primary: "#FFC107",
success: "#228B22",
warning: "#FFA500",
error: "#DC3545",
info: "#177DDC",
dark: isDark ? "#DADADA" : "#212121",
};
return palette[color];
};
const getSize = (size: ProgressSize): number => {
const sizes: Record<ProgressSize, number> = {
xs: 6,
sm: 8,
md: 12,
lg: 16,
xl: 20,
};
return sizes[size];
};
const ProgressCustom: React.FC<ProgressProps> = ({
value = 0,
color = "primary",
size = "md",
radius = 999,
style,
animated = true,
label,
showLabel = true,
}) => {
const { dark } = useTheme();
const isDark = dark ?? false;
const barHeight = getSize(size);
const progressColor = getColor(color, isDark);
const displayValue =
typeof value === "number" ? Math.max(0, Math.min(100, value)) : 0;
// Animasi indeterminate
const translateX = React.useRef(new Animated.Value(-1)).current;
React.useEffect(() => {
if (value === null && animated) {
const animation = Animated.loop(
Animated.timing(translateX, {
toValue: 1,
duration: 1200,
useNativeDriver: true,
})
);
animation.start();
return () => animation.stop();
}
}, [value, animated, translateX]);
const isIndeterminate = value === null;
// Tentukan teks label
const labelText =
label !== undefined
? label
: typeof value === "number"
? `${Math.round(value)}%`
: "";
return (
<View
style={[
styles.container,
{
height: barHeight,
borderRadius: radius,
backgroundColor: isDark
? "rgb(255, 255, 255)"
: "rgba(255, 255, 255, 0.84)",
},
style,
]}
>
{/* Progress Fill */}
{isIndeterminate ? (
<Animated.View
style={[
styles.indeterminateBar,
{
width: "50%",
height: barHeight,
borderRadius: radius,
backgroundColor: progressColor,
transform: [
{
translateX: translateX.interpolate({
inputRange: [-1, 1],
outputRange: [-100, 100],
}),
},
],
},
]}
/>
) : (
<View
style={[
styles.progressBar,
{
width: `${displayValue}%`,
height: barHeight,
borderRadius: radius,
backgroundColor: progressColor,
},
]}
/>
)}
{/* Label di tengah */}
{showLabel && labelText ? (
<View style={StyleSheet.absoluteFill}>
<Text
style={[
styles.label,
{
fontSize: barHeight * 0.7,
lineHeight: barHeight,
color: isDark ? "black" : "black", // Warna teks, bisa disesuaikan
fontWeight: "600",
},
]}
numberOfLines={1}
adjustsFontSizeToFit
>
{labelText}
</Text>
</View>
) : null}
</View>
);
};
export default ProgressCustom;
const styles = StyleSheet.create({
container: {
overflow: "hidden",
backgroundColor: "rgba(0,0,0,0.1)",
width: "100%",
justifyContent: "center", // Pusatkan label secara vertikal
},
progressBar: {
backgroundColor: "#007BFF",
},
indeterminateBar: {
position: "absolute",
top: 0,
left: 0,
backgroundColor: "#007BFF",
},
label: {
textAlign: "center",
width: "100%",
// Hindari overlap dengan background — bisa tambahkan shadow atau background jika perlu
textShadowColor: "rgba(255,255,255,0.6)",
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 1,
},
});

View File

@@ -6,6 +6,7 @@ import ButtonCustom from "../Button/ButtonCustom";
interface ButtonData {
id: string | number;
label: string;
value: string;
}
interface ScrollableCustomProps {
@@ -27,7 +28,7 @@ const ScrollableCustom = ({
style={styles.scrollView}
>
{data.map((item) => {
const isActive = activeId === item.id;
const isActive = activeId === item.value;
return (
<ButtonCustom
@@ -48,6 +49,9 @@ export default ScrollableCustom;
const styles = StyleSheet.create({
scrollView: {
backgroundColor: MainColor.soft_darkblue,
borderRadius: 50,
padding: 5,
// maxHeight: 50,
},
buttonContainer: {

View File

@@ -3,6 +3,7 @@ import {
TEXT_SIZE_LARGE,
TEXT_SIZE_MEDIUM,
TEXT_SIZE_SMALL,
TEXT_SIZE_XLARGE,
} from "@/constants/constans-value";
import React from "react";
import {
@@ -21,7 +22,7 @@ interface TextCustomProps {
style?: StyleProp<TextStyle>;
bold?: boolean;
semiBold?: boolean;
size?: "default" | "large" | "small";
size?: "default" | "large" | "small" | "xlarge";
color?: "default" | "yellow" | "red" | "gray" | "green" | "black"
align?: TextAlign; // Prop untuk alignment
truncate?: boolean | number;
@@ -51,6 +52,7 @@ const TextCustom: React.FC<TextCustomProps> = ({
// Size
if (size === "large") selectedStyles.push(styles.large);
else if (size === "xlarge") selectedStyles.push(styles.xlarge);
else if (size === "small") selectedStyles.push(styles.small);
// Color
@@ -113,11 +115,14 @@ export const styles = StyleSheet.create({
fontFamily: "Poppins-SemiBold",
fontWeight: "500",
},
small: {
fontSize: TEXT_SIZE_SMALL,
},
large: {
fontSize: TEXT_SIZE_LARGE,
},
small: {
fontSize: TEXT_SIZE_SMALL,
xlarge: {
fontSize: TEXT_SIZE_XLARGE,
},
yellow: {
color: MainColor.yellow,

View File

@@ -0,0 +1,13 @@
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
export default function IconArchive({ color }: { color?: string }) {
return (
<Ionicons
name="archive"
size={ICON_SIZE_SMALL}
color={color || MainColor.white}
/>
);
}

View File

@@ -0,0 +1,14 @@
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
export default function IconContribution({ color }: { color?: string }) {
return (
<>
<Ionicons
size={ICON_SIZE_SMALL}
name="people"
color={color || "white"}
/>
</>
);
}

View File

@@ -0,0 +1,10 @@
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { FontAwesome5 } from "@expo/vector-icons";
export default function IconEdit() {
return (
<>
<FontAwesome5 name="edit" size={ICON_SIZE_SMALL} color="white" />
</>
);
}

View File

@@ -0,0 +1,9 @@
import { FontAwesome5 } from "@expo/vector-icons";
export default function IconHistory({ color }: { color?: string }) {
return (
<>
<FontAwesome5 size={20} name="history" color={color} />
</>
);
}

View File

@@ -0,0 +1,10 @@
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
export default function IconHome({ color }: { color?: string }) {
return (
<>
<Ionicons name="home" size={ICON_SIZE_SMALL} color={color || "white"} />
</>
);
}

View File

@@ -0,0 +1,12 @@
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { MaterialIcons } from "@expo/vector-icons";
export default function IconStatus({ color }: { color?: string }) {
return (
<MaterialIcons
size={ICON_SIZE_SMALL}
name="checklist-rtl"
color={color || "white"}
/>
);
}

15
components/_Icon/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import IconContribution from "./IconContribution";
import IconEdit from "./IconEdit";
import IconHistory from "./IconHistory";
import IconHome from "./IconHome";
import IconStatus from "./IconStatus";
import IconArchive from "./IconArchive";
export {
IconContribution,
IconEdit,
IconHistory,
IconHome,
IconStatus,
IconArchive,
};

View File

@@ -0,0 +1,48 @@
import { ImageSourcePropType } from "react-native";
import Divider from "../Divider/Divider";
import Grid from "../Grid/GridCustom";
import AvatarCustom from "../Image/AvatarCustom";
import TextCustom from "../Text/TextCustom";
const AvatarUsernameAndOtherComponent = ({
avatarHref,
avatar,
name,
rightComponent,
withBottomLine = false,
}: {
avatarHref?: string;
avatar?: ImageSourcePropType;
name?: string;
rightComponent?: React.ReactNode;
withBottomLine?: boolean;
}) => {
return (
<>
<Grid containerStyle={{ zIndex: 10 }}>
<Grid.Col span={2}>
<AvatarCustom source={avatar} href={avatarHref as any} />
</Grid.Col>
<Grid.Col
span={rightComponent ? 6 : 10}
style={{ justifyContent: "center" }}
>
<TextCustom truncate bold>
{name || "Username"}
</TextCustom>
</Grid.Col>
{rightComponent && (
<Grid.Col
span={4}
style={{ alignItems: "flex-end", justifyContent: "center" }}
>
{rightComponent}
</Grid.Col>
)}
</Grid>
{withBottomLine && <Divider marginTop={0} />}
</>
);
};
export default AvatarUsernameAndOtherComponent;

View File

@@ -0,0 +1,32 @@
import { AccentColor } from "@/constants/color-palet";
import DUMMY_IMAGE from "@/constants/dummy-image-value";
import { Image } from "expo-image";
import { StyleSheet } from "react-native";
import ClickableCustom from "../Clickable/ClickableCustom";
import { router } from "expo-router";
export default function DummyLandscapeImage() {
return (
<ClickableCustom
onPress={() => {
router.push("/(application)/(image)/preview-image/1");
}}
>
<Image source={DUMMY_IMAGE.background} style={styles.backgroundImage} />
</ClickableCustom>
);
}
const styles = StyleSheet.create({
backgroundImage: {
width: "100%",
height: 200, // Tinggi background sesuai kebutuhan
justifyContent: "center",
alignItems: "center",
borderRadius: 6,
overflow: "hidden",
borderWidth: 1,
borderColor: AccentColor.blue,
backgroundColor: "white",
},
});

View File

@@ -0,0 +1,39 @@
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import TextInputCustom from "../TextInput/TextInputCustom";
import { Ionicons } from "@expo/vector-icons";
import { StyleProp, ViewStyle, TextStyle } from "react-native";
interface SearchInputProps {
placeholder?: string;
onPress?: () => void;
iconLeft?: React.ReactNode;
iconRight?: React.ReactNode;
containerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<TextStyle>;
}
export default function SearchInput({
placeholder,
onPress,
iconLeft,
iconRight,
containerStyle = { marginBottom: 0 },
style,
...props
}: SearchInputProps) {
return (
<TextInputCustom
iconLeft={
<Ionicons
name="search-outline"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder={placeholder}
borderRadius={50}
containerStyle={containerStyle}
{...props}
/>
);
}

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