Compare commits
40 Commits
resourcing
...
invesment/
| Author | SHA1 | Date | |
|---|---|---|---|
| a43ddaa9d6 | |||
| 927db87749 | |||
| 8a514d2670 | |||
| 554428b7b4 | |||
| befbd1a47d | |||
| c9a1ac1db5 | |||
| 4bcfcb5f5a | |||
| f21ff744d3 | |||
| b18044f53f | |||
| 20258d1fe5 | |||
| 51d696128e | |||
| 1b1732c7d8 | |||
| 7528c449eb | |||
| ed87d4a3f3 | |||
| 603003865b | |||
| e02ae8e35d | |||
| 4f8ae2d7e0 | |||
| 360ac5807c | |||
| 8cb0054580 | |||
| 64d5a4308c | |||
| 30d61c84aa | |||
| 70e324e76e | |||
| 4474b46ff3 | |||
| aa4ea9fb0c | |||
| 81d86885f4 | |||
| 814f261881 | |||
| 7889d25a44 | |||
| ce0e82e627 | |||
| 08dfd62bfd | |||
| c8cc0f0232 | |||
| b844a8151d | |||
| f9e96aa077 | |||
| b8b1efc71e | |||
| eaf0ebfb0a | |||
| e68d366d49 | |||
| 9999f78ed4 | |||
| 2be5afe5ca | |||
| 24913a9f97 | |||
| 3376336c55 | |||
| a0dad5618a |
@@ -76,6 +76,13 @@ export default function UserLayout() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="event/create"
|
||||
options={{
|
||||
title: "Tambah Event",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/detail/[id]"
|
||||
@@ -85,6 +92,167 @@ 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 />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Investment Section ========= */}
|
||||
|
||||
{/* ========== Donation Section ========= */}
|
||||
<Stack.Screen
|
||||
name="donation/create"
|
||||
options={{
|
||||
title: "Tambah Donasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Donation Section ========= */}
|
||||
|
||||
{/* ========== Job Section ========= */}
|
||||
<Stack.Screen
|
||||
name="job/create"
|
||||
options={{
|
||||
title: "Tambah Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/(tabs)"
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
headerLeft: () => <BackButton path="/home" />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/index"
|
||||
options={{
|
||||
title: "Detail Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="job/[id]/edit"
|
||||
options={{
|
||||
title: "Edit Job",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== End Job Section ========= */}
|
||||
|
||||
{/* ========== Forum Section ========= */}
|
||||
<Stack.Screen
|
||||
name="forum/create"
|
||||
@@ -104,7 +272,7 @@ export default function UserLayout() {
|
||||
name="forum/[id]/forumku"
|
||||
options={{
|
||||
title: "Forumku",
|
||||
headerLeft: () => <BackButton icon={'close'} />,
|
||||
headerLeft: () => <BackButton icon={"close"} />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
@@ -114,6 +282,34 @@ export default function UserLayout() {
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/report-commentar"
|
||||
options={{
|
||||
title: "Laporkan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/other-report-commentar"
|
||||
options={{
|
||||
title: "Laporkan Komentar",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/report-posting"
|
||||
options={{
|
||||
title: "Laporkan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="forum/[id]/other-report-posting"
|
||||
options={{
|
||||
title: "Laporkan Diskusi",
|
||||
headerLeft: () => <BackButton />,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ========== Maps Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
36
app/(application)/(user)/collaboration/(tabs)/_layout.tsx
Normal file
36
app/(application)/(user)/collaboration/(tabs)/_layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
68
app/(application)/(user)/collaboration/(tabs)/group.tsx
Normal file
68
app/(application)/(user)/collaboration/(tabs)/group.tsx
Normal 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;
|
||||
}
|
||||
28
app/(application)/(user)/collaboration/(tabs)/index.tsx
Normal file
28
app/(application)/(user)/collaboration/(tabs)/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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.",
|
||||
},
|
||||
];
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
53
app/(application)/(user)/collaboration/[id]/edit.tsx
Normal file
53
app/(application)/(user)/collaboration/[id]/edit.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
91
app/(application)/(user)/collaboration/[id]/index.tsx
Normal file
91
app/(application)/(user)/collaboration/[id]/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
53
app/(application)/(user)/collaboration/create.tsx
Normal file
53
app/(application)/(user)/collaboration/create.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
68
app/(application)/(user)/crowdfunding/index.tsx
Normal file
68
app/(application)/(user)/crowdfunding/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
11
app/(application)/(user)/donation/create.tsx
Normal file
11
app/(application)/(user)/donation/create.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TextCustom, ViewWrapper } from "@/components";
|
||||
|
||||
export default function DonationCreate() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<TextCustom bold size="large">
|
||||
Coming Soon !
|
||||
</TextCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,62 +1,41 @@
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
IconContribution,
|
||||
IconHistory,
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Tabs } from "expo-router";
|
||||
|
||||
export default function EventLayout() {
|
||||
export default function EventTabsLayout() {
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: MainColor.yellow,
|
||||
tabBarInactiveTintColor: MainColor.white_gray,
|
||||
tabBarStyle: {
|
||||
backgroundColor: MainColor.darkblue,
|
||||
},
|
||||
// tabBarButton: HapticTab,
|
||||
// tabBarBackground: BlurTabBarBackground,
|
||||
// tabBarStyle: Platform.select({
|
||||
// ios: {
|
||||
// // Use a transparent background on iOS to show the blur effect
|
||||
// position: "absolute",
|
||||
// },
|
||||
// default: {},
|
||||
// }),
|
||||
}}
|
||||
>
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Home",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="home" color={color} />
|
||||
),
|
||||
title: "Beranda",
|
||||
tabBarIcon: ({ color }) => <IconHome color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="status"
|
||||
options={{
|
||||
title: "Status",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="list" color={color} />
|
||||
),
|
||||
tabBarIcon: ({ color }) => <IconStatus color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="kontribusi"
|
||||
name="contribution"
|
||||
options={{
|
||||
title: "Kontribusi",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<Ionicons size={20} name="extension-puzzle" color={color} />
|
||||
),
|
||||
tabBarIcon: ({ color }) => <IconContribution color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="riwayat"
|
||||
name="history"
|
||||
options={{
|
||||
title: "Riwayat",
|
||||
tabBarIcon: ({ color }) => (
|
||||
<FontAwesome5 size={20} name="history" color={color} />
|
||||
),
|
||||
tabBarIcon: ({ color }) => <IconHistory color={color} />,
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
43
app/(application)/(user)/event/(tabs)/contribution.tsx
Normal file
43
app/(application)/(user)/event/(tabs)/contribution.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
68
app/(application)/(user)/event/(tabs)/history.tsx
Normal file
68
app/(application)/(user)/event/(tabs)/history.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +1,23 @@
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import FloatingButton from "@/components/Button/FloatingButton";
|
||||
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
|
||||
import { router } from "expo-router";
|
||||
import { Text, TouchableHighlight, View } from "react-native";
|
||||
|
||||
export default function Event() {
|
||||
export default function EventBeranda() {
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<TouchableHighlight onPress={() => router.push("/event/detail/1")}>
|
||||
<View
|
||||
style={{
|
||||
padding: 20,
|
||||
backgroundColor: MainColor.darkblue,
|
||||
borderRadius: 10,
|
||||
borderColor: AccentColor.blue,
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<Text style={GStyles.textLabel}>Event</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<ViewWrapper
|
||||
hideFooter
|
||||
floatingButton={
|
||||
<FloatingButton onPress={() => router.push("/event/create")} />
|
||||
}
|
||||
>
|
||||
{Array.from({ length: 10 }).map((_, index) => (
|
||||
<Event_BoxPublishSection
|
||||
key={index}
|
||||
id={index.toString()}
|
||||
href={`/event/${index}/publish`}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
117
app/(application)/(user)/event/[id]/[status]/detail-event.tsx
Normal file
117
app/(application)/(user)/event/[id]/[status]/detail-event.tsx
Normal 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!",
|
||||
},
|
||||
];
|
||||
51
app/(application)/(user)/event/[id]/contribution.tsx
Normal file
51
app/(application)/(user)/event/[id]/contribution.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
107
app/(application)/(user)/event/[id]/edit.tsx
Normal file
107
app/(application)/(user)/event/[id]/edit.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
51
app/(application)/(user)/event/[id]/history.tsx
Normal file
51
app/(application)/(user)/event/[id]/history.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
17
app/(application)/(user)/event/[id]/list-of-participants.tsx
Normal file
17
app/(application)/(user)/event/[id]/list-of-participants.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
64
app/(application)/(user)/event/[id]/publish.tsx
Normal file
64
app/(application)/(user)/event/[id]/publish.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
109
app/(application)/(user)/event/create.tsx
Normal file
109
app/(application)/(user)/event/create.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
ButtonCustom,
|
||||
SelectCustom,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
TextInputCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
|
||||
import { masterTypeEvent } from "@/lib/dummy-data/event/master-type-event";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router } from "expo-router";
|
||||
import React, { useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export default function EventCreate() {
|
||||
const [selectedDate, setSelectedDate] = useState<
|
||||
Date | DateTimePickerEvent | null
|
||||
>(null);
|
||||
|
||||
const [selectedEndDate, setSelectedEndDate] = useState<
|
||||
Date | DateTimePickerEvent | null
|
||||
>(null);
|
||||
|
||||
const handlerSubmit = () => {
|
||||
try {
|
||||
if (selectedDate) {
|
||||
console.log("Tanggal yang dipilih:", selectedDate);
|
||||
console.log(`ISO Format ${Platform.OS}:`, selectedDate.toString());
|
||||
// Kirim ke API atau proses lanjutan
|
||||
} else {
|
||||
console.log("Tanggal belum dipilih");
|
||||
}
|
||||
|
||||
if (selectedEndDate) {
|
||||
console.log("Tanggal yang dipilih:", selectedEndDate);
|
||||
console.log(`ISO Format ${Platform.OS}:`, selectedEndDate.toString());
|
||||
// Kirim ke API atau proses lanjutan
|
||||
} else {
|
||||
console.log("Tanggal berakhir belum dipilih");
|
||||
}
|
||||
|
||||
console.log("Data berhasil disimpan");
|
||||
router.navigate("/event/status");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonSubmit = (
|
||||
<ButtonCustom title="Simpan" onPress={handlerSubmit} />
|
||||
// <BoxButtonOnFooter>
|
||||
// </BoxButtonOnFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom gap={"xs"}>
|
||||
<TextInputCustom
|
||||
placeholder="Masukkan nama event"
|
||||
label="Nama Event"
|
||||
required
|
||||
/>
|
||||
<SelectCustom
|
||||
label="Tipe Event"
|
||||
placeholder="Pilih tipe event"
|
||||
data={masterTypeEvent}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<TextInputCustom
|
||||
label="Lokasi"
|
||||
placeholder="Masukkan lokasi event"
|
||||
required
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
label="Tanggal & Waktu Mulai"
|
||||
required
|
||||
onChange={(date: Date) => {
|
||||
setSelectedDate(date as any);
|
||||
}}
|
||||
value={selectedDate as any}
|
||||
minimumDate={new Date(Date.now())}
|
||||
/>
|
||||
|
||||
<DateTimePickerCustom
|
||||
label="Tanggal & Waktu Berakhir"
|
||||
required
|
||||
onChange={(date: Date) => {
|
||||
setSelectedEndDate(date as any);
|
||||
}}
|
||||
value={selectedEndDate as any}
|
||||
/>
|
||||
|
||||
<TextAreaCustom
|
||||
label="Deskripsi"
|
||||
placeholder="Masukkan deskripsi event"
|
||||
required
|
||||
showCount
|
||||
maxLength={100}
|
||||
/>
|
||||
|
||||
{buttonSubmit}
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { listDataDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
|
||||
import { listDummyDiscussionForum } from "@/screens/Forum/list-data-dummy";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
@@ -47,12 +47,14 @@ export default function Forumku() {
|
||||
</ButtonCustom>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{listDataDummyCommentarForum.map((e, i) => (
|
||||
{listDummyDiscussionForum.map((e, i) => (
|
||||
<Forum_BoxDetailSection
|
||||
key={i}
|
||||
data={e}
|
||||
setOpenDrawer={setOpenDrawer}
|
||||
setStatus={setStatus}
|
||||
isTruncate={true}
|
||||
href={`/forum/${id}`}
|
||||
/>
|
||||
))}
|
||||
</StackCustom>
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
ButtonCustom,
|
||||
DrawerCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
@@ -12,9 +11,9 @@ import Forum_CommentarBoxSection from "@/screens/Forum/CommentarBoxSection";
|
||||
import Forum_BoxDetailSection from "@/screens/Forum/DiscussionBoxSection";
|
||||
import { listDummyCommentarForum } from "@/screens/Forum/list-data-dummy";
|
||||
import Forum_MenuDrawerBerandaSection from "@/screens/Forum/MenuDrawerSection.tsx/MenuBeranda";
|
||||
import Forum_MenuDrawerCommentar from "@/screens/Forum/MenuDrawerSection.tsx/MenuCommentar";
|
||||
import { router, useLocalSearchParams } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Divider } from "react-native-paper";
|
||||
|
||||
export default function ForumDetail() {
|
||||
const { id } = useLocalSearchParams();
|
||||
@@ -27,7 +26,7 @@ export default function ForumDetail() {
|
||||
|
||||
// Comentar
|
||||
const [openDrawerCommentar, setOpenDrawerCommentar] = useState(false);
|
||||
const [statusCommentar, setStatusCommentar] = useState("");
|
||||
const [alertDeleteCommentar, setAlertDeleteCommentar] = useState(false);
|
||||
|
||||
const dataDummy = {
|
||||
name: "Bagas",
|
||||
@@ -78,7 +77,6 @@ export default function ForumDetail() {
|
||||
key={i}
|
||||
data={e}
|
||||
setOpenDrawer={setOpenDrawerCommentar}
|
||||
setStatus={setStatusCommentar}
|
||||
/>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
@@ -145,16 +143,34 @@ export default function ForumDetail() {
|
||||
isVisible={openDrawerCommentar}
|
||||
closeDrawer={() => setOpenDrawerCommentar(false)}
|
||||
>
|
||||
<Forum_MenuDrawerBerandaSection
|
||||
<Forum_MenuDrawerCommentar
|
||||
id={id as string}
|
||||
status={statusCommentar}
|
||||
setIsDrawerOpen={() => {
|
||||
setOpenDrawerCommentar(false);
|
||||
}}
|
||||
setShowDeleteAlert={setDeleteAlert}
|
||||
setShowAlertStatus={setAlertStatus}
|
||||
setShowDeleteAlert={setAlertDeleteCommentar}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Delete Commentar */}
|
||||
<AlertCustom
|
||||
isVisible={alertDeleteCommentar}
|
||||
title="Hapus Komentar"
|
||||
message="Apakah Anda yakin ingin menghapus komentar ini?"
|
||||
onLeftPress={() => {
|
||||
setOpenDrawerCommentar(false);
|
||||
setAlertDeleteCommentar(false);
|
||||
console.log("Batal");
|
||||
}}
|
||||
onRightPress={() => {
|
||||
setOpenDrawerCommentar(false);
|
||||
setAlertDeleteCommentar(false);
|
||||
console.log("Hapus commentar");
|
||||
}}
|
||||
textLeft="Batal"
|
||||
textRight="Hapus"
|
||||
colorRight={MainColor.red}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router } from "expo-router";
|
||||
|
||||
export default function ForumOtherReportCommentar() {
|
||||
const handleSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report lainnya");
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={handleSubmit}>
|
||||
<TextAreaCustom placeholder="Laporkan Komentar" />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
32
app/(application)/(user)/forum/[id]/other-report-posting.tsx
Normal file
32
app/(application)/(user)/forum/[id]/other-report-posting.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
BoxButtonOnFooter,
|
||||
ButtonCustom,
|
||||
TextAreaCustom,
|
||||
ViewWrapper,
|
||||
} from "@/components";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { router } from "expo-router";
|
||||
|
||||
export default function ForumOtherReportPosting() {
|
||||
const handleSubmit = (
|
||||
<BoxButtonOnFooter>
|
||||
<ButtonCustom
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report lainnya");
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
</BoxButtonOnFooter>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper footerComponent={handleSubmit}>
|
||||
<TextAreaCustom placeholder="Laporkan Diskusi" />
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
42
app/(application)/(user)/forum/[id]/report-commentar.tsx
Normal file
42
app/(application)/(user)/forum/[id]/report-commentar.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
ButtonCustom,
|
||||
Spacing,
|
||||
StackCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
|
||||
import { router } from "expo-router";
|
||||
|
||||
export default function ForumReportCommentar() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<Forum_ReportListSection />
|
||||
<ButtonCustom
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report");
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
backgroundColor={AccentColor.blue}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Lainnya");
|
||||
router.replace("/forum/[id]/other-report-commentar");
|
||||
}}
|
||||
>
|
||||
Lainnya
|
||||
</ButtonCustom>
|
||||
<Spacing/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
37
app/(application)/(user)/forum/[id]/report-posting.tsx
Normal file
37
app/(application)/(user)/forum/[id]/report-posting.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ViewWrapper, StackCustom, ButtonCustom, Spacing } from "@/components";
|
||||
import { MainColor, AccentColor } from "@/constants/color-palet";
|
||||
import Forum_ReportListSection from "@/screens/Forum/ReportListSection";
|
||||
import { router } from "expo-router";
|
||||
|
||||
export default function ForumReportPosting() {
|
||||
return (
|
||||
<>
|
||||
<ViewWrapper>
|
||||
<StackCustom>
|
||||
<Forum_ReportListSection />
|
||||
<ButtonCustom
|
||||
backgroundColor={MainColor.red}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Report");
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</ButtonCustom>
|
||||
<ButtonCustom
|
||||
backgroundColor={AccentColor.blue}
|
||||
textColor={MainColor.white}
|
||||
onPress={() => {
|
||||
console.log("Lainnya");
|
||||
router.replace("/forum/[id]/other-report-posting");
|
||||
}}
|
||||
>
|
||||
Lainnya
|
||||
</ButtonCustom>
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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={() =>
|
||||
|
||||
59
app/(application)/(user)/investment/(tabs)/_layout.tsx
Normal file
59
app/(application)/(user)/investment/(tabs)/_layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
81
app/(application)/(user)/investment/(tabs)/index.tsx
Normal file
81
app/(application)/(user)/investment/(tabs)/index.tsx
Normal 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}>
|
||||
<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>;
|
||||
49
app/(application)/(user)/investment/(tabs)/my-holding.tsx
Normal file
49
app/(application)/(user)/investment/(tabs)/my-holding.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
70
app/(application)/(user)/investment/(tabs)/portofolio.tsx
Normal file
70
app/(application)/(user)/investment/(tabs)/portofolio.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
BaseBox,
|
||||
Grid,
|
||||
ScrollableCustom,
|
||||
Spacing,
|
||||
TextCustom,
|
||||
ViewWrapper
|
||||
} from "@/components";
|
||||
import DUMMY_IMAGE from "@/constants/dummy-image-value";
|
||||
import { masterStatus } from "@/lib/dummy-data/_master/status";
|
||||
import { Image } from "expo-image";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
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) => (
|
||||
<BaseBox key={index} paddingTop={7} paddingBottom={7}>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextCustom truncate={2}>
|
||||
Title here : {activeCategory} 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 />
|
||||
|
||||
<TextCustom bold size="small">
|
||||
Target Dana:
|
||||
</TextCustom>
|
||||
<TextCustom>Rp. {index + 1 % 3/4 * 1004000}</TextCustom>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1}>
|
||||
<View />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5}>
|
||||
<Image
|
||||
source={DUMMY_IMAGE.background}
|
||||
style={{ width: "auto", height: 100, borderRadius: 10 }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</BaseBox>
|
||||
))}
|
||||
</ViewWrapper>
|
||||
);
|
||||
}
|
||||
53
app/(application)/(user)/investment/(tabs)/transaction.tsx
Normal file
53
app/(application)/(user)/investment/(tabs)/transaction.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
197
app/(application)/(user)/investment/create.tsx
Normal file
197
app/(application)/(user)/investment/create.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
34
app/(application)/(user)/job/(tabs)/_layout.tsx
Normal file
34
app/(application)/(user)/job/(tabs)/_layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
16
app/(application)/(user)/job/(tabs)/archive.tsx
Normal file
16
app/(application)/(user)/job/(tabs)/archive.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
34
app/(application)/(user)/job/(tabs)/index.tsx
Normal file
34
app/(application)/(user)/job/(tabs)/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
50
app/(application)/(user)/job/(tabs)/status.tsx
Normal file
50
app/(application)/(user)/job/(tabs)/status.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
65
app/(application)/(user)/job/[id]/[status]/detail.tsx
Normal file
65
app/(application)/(user)/job/[id]/[status]/detail.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
67
app/(application)/(user)/job/[id]/edit.tsx
Normal file
67
app/(application)/(user)/job/[id]/edit.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
79
app/(application)/(user)/job/[id]/index.tsx
Normal file
79
app/(application)/(user)/job/[id]/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
73
app/(application)/(user)/job/create.tsx
Normal file
73
app/(application)/(user)/job/create.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DrawerCustom } from "@/components";
|
||||
import { AlertCustom, DrawerCustom } from "@/components";
|
||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
@@ -7,13 +7,14 @@ import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
|
||||
import PorfofolioSection from "@/screens/Portofolio/PorfofolioSection";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Stack, useLocalSearchParams } from "expo-router";
|
||||
import { Stack, useLocalSearchParams, router } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { TouchableOpacity } from "react-native";
|
||||
|
||||
export default function Portofolio() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const [deleteAlert, setDeleteAlert] = useState(false);
|
||||
|
||||
const openDrawer = () => {
|
||||
setIsDrawerOpen(true);
|
||||
@@ -42,7 +43,7 @@ export default function Portofolio() {
|
||||
headerTitleStyle: GStyles.headerTitleStyle,
|
||||
}}
|
||||
/>
|
||||
<PorfofolioSection />
|
||||
<PorfofolioSection setShowDeleteAlert={setDeleteAlert} />
|
||||
</ViewWrapper>
|
||||
|
||||
{/* Drawer Komponen Eksternal */}
|
||||
@@ -56,6 +57,22 @@ export default function Portofolio() {
|
||||
setIsDrawerOpen={setIsDrawerOpen}
|
||||
/>
|
||||
</DrawerCustom>
|
||||
|
||||
{/* Alert Delete */}
|
||||
<AlertCustom
|
||||
isVisible={deleteAlert}
|
||||
onLeftPress={() => setDeleteAlert(false)}
|
||||
onRightPress={() => {
|
||||
setDeleteAlert(false);
|
||||
console.log("Hapus portofolio");
|
||||
router.back();
|
||||
}}
|
||||
title="Hapus Portofolio"
|
||||
message="Apakah Anda yakin ingin menghapus portofolio ini?"
|
||||
textLeft="Batal"
|
||||
textRight="Hapus"
|
||||
colorRight={MainColor.red}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,14 +103,7 @@ export default function CreateProfile() {
|
||||
required
|
||||
onChange={(value) => setData({ ...(data as any), gender: value })}
|
||||
/>
|
||||
<TextInputCustom
|
||||
required
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat"
|
||||
value={data.address}
|
||||
onChangeText={(text) => setData({ ...data, address: text })}
|
||||
/>
|
||||
{/* <Spacing /> */}
|
||||
<Spacing />
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -22,15 +22,8 @@ export default function ProfileEdit() {
|
||||
});
|
||||
|
||||
const options = [
|
||||
{ label: "React", value: "react" },
|
||||
{ label: "Vue", value: "vue" },
|
||||
{ label: "Angular", value: "angular" },
|
||||
{ label: "Svelte", value: "svelte" },
|
||||
{ label: "Next.js", value: "nextjs" },
|
||||
{ label: "Nuxt.js", value: "nuxtjs" },
|
||||
{ label: "Remix", value: "remix" },
|
||||
{ label: "Sapper", value: "sapper" },
|
||||
{ label: "SvelteKit", value: "sveltekit" },
|
||||
{ label: "Laki-laki", value: "laki-laki" },
|
||||
{ label: "Perempuan", value: "perempuan" },
|
||||
];
|
||||
|
||||
const handleSave = () => {
|
||||
@@ -59,16 +52,6 @@ export default function ProfileEdit() {
|
||||
}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<SelectCustom
|
||||
label="Framework"
|
||||
placeholder="Pilih framework favoritmu"
|
||||
data={options}
|
||||
value={data.selectedValue}
|
||||
onChange={(value) => {
|
||||
setData({ ...(data as any), selectedValue: value });
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInputCustom
|
||||
label="Nama"
|
||||
placeholder="Nama"
|
||||
@@ -96,6 +79,16 @@ export default function ProfileEdit() {
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<SelectCustom
|
||||
required
|
||||
label="Jenis Kelamin"
|
||||
placeholder="Pilih jenis kelamin"
|
||||
data={options}
|
||||
value={data.selectedValue}
|
||||
onChange={(value) => {
|
||||
setData({ ...(data as any), selectedValue: value });
|
||||
}}
|
||||
/>
|
||||
</StackCustom>
|
||||
</ViewWrapper>
|
||||
);
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
43
app/(application)/(user)/voting/(tabs)/_layout.tsx
Normal file
43
app/(application)/(user)/voting/(tabs)/_layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
17
app/(application)/(user)/voting/(tabs)/contribution.tsx
Normal file
17
app/(application)/(user)/voting/(tabs)/contribution.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
37
app/(application)/(user)/voting/(tabs)/history.tsx
Normal file
37
app/(application)/(user)/voting/(tabs)/history.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
23
app/(application)/(user)/voting/(tabs)/index.tsx
Normal file
23
app/(application)/(user)/voting/(tabs)/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
60
app/(application)/(user)/voting/(tabs)/status.tsx
Normal file
60
app/(application)/(user)/voting/(tabs)/status.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
108
app/(application)/(user)/voting/[id]/[status]/detail.tsx
Normal file
108
app/(application)/(user)/voting/[id]/[status]/detail.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
65
app/(application)/(user)/voting/[id]/contribution.tsx
Normal file
65
app/(application)/(user)/voting/[id]/contribution.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
78
app/(application)/(user)/voting/[id]/edit.tsx
Normal file
78
app/(application)/(user)/voting/[id]/edit.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
64
app/(application)/(user)/voting/[id]/history.tsx
Normal file
64
app/(application)/(user)/voting/[id]/history.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
87
app/(application)/(user)/voting/[id]/index.tsx
Normal file
87
app/(application)/(user)/voting/[id]/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
26
app/(application)/(user)/voting/[id]/list-of-contributor.tsx
Normal file
26
app/(application)/(user)/voting/[id]/list-of-contributor.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
78
app/(application)/(user)/voting/create.tsx
Normal file
78
app/(application)/(user)/voting/create.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
258
app/(application)/coba/double-scroll.tsx
Normal file
258
app/(application)/coba/double-scroll.tsx
Normal 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",
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
99
app/(application)/coba/upload-button.tsx
Normal file
99
app/(application)/coba/upload-button.tsx
Normal 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;
|
||||
BIN
assets/images/constants/crowd-hipmi.png
Normal file
BIN
assets/images/constants/crowd-hipmi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 331 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
BIN
assets/images/dummy/dummy-user.png
Normal file
BIN
assets/images/dummy/dummy-user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 395 KiB |
13
bun.lock
13
bun.lock
@@ -5,6 +5,7 @@
|
||||
"name": "hipmi-mobile",
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.1.0",
|
||||
"@react-native-community/datetimepicker": "8.4.1",
|
||||
"@react-navigation/bottom-tabs": "^7.4.2",
|
||||
"@react-navigation/drawer": "^7.5.2",
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
@@ -15,7 +16,10 @@
|
||||
"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",
|
||||
@@ -34,6 +38,7 @@
|
||||
"react-native-international-phone-number": "^0.9.3",
|
||||
"react-native-maps": "1.20.1",
|
||||
"react-native-otp-entry": "^1.8.5",
|
||||
"react-native-pager-view": "6.7.1",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
@@ -372,6 +377,8 @@
|
||||
|
||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
|
||||
|
||||
"@react-native-community/datetimepicker": ["@react-native-community/datetimepicker@8.4.1", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "expo": ">=52.0.0", "react": "*", "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["expo", "react-native-windows"] }, "sha512-DrK+CUS5fZnz8dhzBezirkzQTcNDdaXer3oDLh0z4nc2tbdIdnzwvXCvi8IEOIvleoc9L95xS5tKUl0/Xv71Mg=="],
|
||||
|
||||
"@react-native/assets-registry": ["@react-native/assets-registry@0.79.5", "", {}, "sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w=="],
|
||||
|
||||
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.79.5", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.79.5" } }, "sha512-Rt/imdfqXihD/sn0xnV4flxxb1aLLjPtMF1QleQjEhJsTUPpH4TFlfOpoCvsrXoDl4OIcB1k4FVM24Ez92zf5w=="],
|
||||
@@ -824,8 +831,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=="],
|
||||
@@ -1386,6 +1397,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=="],
|
||||
|
||||
31
components/Alert/AlertDefaultSystem.ts
Normal file
31
components/Alert/AlertDefaultSystem.ts
Normal 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?.(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
189
components/Badge/BadgeCustom.tsx
Normal file
189
components/Badge/BadgeCustom.tsx
Normal 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;
|
||||
@@ -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,
|
||||
]}
|
||||
|
||||
20
components/Box/BoxWithHeaderInformation.tsx
Normal file
20
components/Box/BoxWithHeaderInformation.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
components/Button/DotButton.tsx
Normal file
13
components/Button/DotButton.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
133
components/Checkbox/CheckboxCustom.tsx
Normal file
133
components/Checkbox/CheckboxCustom.tsx
Normal 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 };
|
||||
75
components/Checkbox/CheckboxGroup.tsx
Normal file
75
components/Checkbox/CheckboxGroup.tsx
Normal 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;
|
||||
|
||||
61
components/Checkbox/checkbox-styles.tsx
Normal file
61
components/Checkbox/checkbox-styles.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
44
components/Container/CircleContainer.tsx
Normal file
44
components/Container/CircleContainer.tsx
Normal 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;
|
||||
205
components/DateInput/DataTimeAndroid.tsx
Normal file
205
components/DateInput/DataTimeAndroid.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// DateTimeInput.tsx
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import DateTimePicker, {
|
||||
DateTimePickerEvent,
|
||||
} from "@react-native-community/datetimepicker";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Pressable, StyleProp, Text, View, ViewStyle } from "react-native";
|
||||
import Grid from "../Grid/GridCustom";
|
||||
import TextCustom from "../Text/TextCustom";
|
||||
|
||||
interface DateTimeInputProps {
|
||||
// Main
|
||||
value?: DateTimePickerEvent;
|
||||
mode?: "date" | "time";
|
||||
onChange: (selectedDate: DateTimePickerEvent) => void;
|
||||
maximumDate?: Date;
|
||||
minimumDate?: Date;
|
||||
// Main
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
iconLeft?: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
borderRadius?: number;
|
||||
externalError?: string;
|
||||
internalError?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const DateTimeInput_Android: React.FC<DateTimeInputProps> = ({
|
||||
// Main
|
||||
value,
|
||||
mode,
|
||||
onChange,
|
||||
maximumDate,
|
||||
minimumDate,
|
||||
// Main
|
||||
label,
|
||||
required,
|
||||
disabled,
|
||||
iconLeft,
|
||||
style,
|
||||
borderRadius = 8,
|
||||
externalError,
|
||||
internalError,
|
||||
containerStyle,
|
||||
}) => {
|
||||
const [showDate, setShowDate] = useState(false);
|
||||
const [showTime, setShowTime] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(value as any);
|
||||
const [selectedTime, setSelectedTime] = useState<Date>(value as any);
|
||||
|
||||
// Fungsi untuk menggabungkan tanggal dan waktu
|
||||
const combineDateAndTime = useCallback((date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
combined.setHours(
|
||||
time.getHours(),
|
||||
time.getMinutes(),
|
||||
time.getSeconds(),
|
||||
time.getMilliseconds()
|
||||
);
|
||||
return combined;
|
||||
}, []);
|
||||
|
||||
// Handler untuk tanggal
|
||||
const handleConfirmDate = (event: DateTimePickerEvent, date?: Date) => {
|
||||
if (event.type === "set" && date) {
|
||||
setSelectedDate(date);
|
||||
if (selectedTime) {
|
||||
const combined = combineDateAndTime(date, selectedTime);
|
||||
onChange?.(combined as any);
|
||||
}
|
||||
}
|
||||
setShowDate(false);
|
||||
};
|
||||
|
||||
// Handler untuk waktu
|
||||
const handleConfirmTime = (event: DateTimePickerEvent, time?: Date) => {
|
||||
if (event.type === "set" && time) {
|
||||
setSelectedTime(time);
|
||||
if (selectedDate) {
|
||||
const combined = combineDateAndTime(selectedDate, time);
|
||||
onChange?.(combined as any);
|
||||
}
|
||||
}
|
||||
setShowTime(false);
|
||||
};
|
||||
|
||||
const toggleDatePicker = () => {
|
||||
setShowDate(!showDate);
|
||||
};
|
||||
|
||||
const toggleTimePicker = () => {
|
||||
setShowTime(!showTime);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={[GStyles.inputContainerArea, containerStyle]}>
|
||||
{label && (
|
||||
<Text style={GStyles.inputLabel}>
|
||||
{label}
|
||||
{required && <Text style={GStyles.inputRequired}> *</Text>}
|
||||
</Text>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
style,
|
||||
{ borderRadius },
|
||||
externalError || internalError ? GStyles.inputErrorBorder : null,
|
||||
GStyles.inputContainerInput,
|
||||
disabled && GStyles.disabledBox,
|
||||
]}
|
||||
>
|
||||
<View style={GStyles.inputIcon}>
|
||||
<Ionicons
|
||||
name="calendar-outline"
|
||||
size={20}
|
||||
color={MainColor.placeholder}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Grid
|
||||
containerStyle={{
|
||||
borderRadius: 8,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Grid.Col span={6} style={{}}>
|
||||
<Pressable onPress={toggleDatePicker}>
|
||||
<TextCustom color="gray">
|
||||
{selectedDate ? (
|
||||
<TextCustom color="black">
|
||||
{selectedDate.toLocaleDateString()}
|
||||
</TextCustom>
|
||||
) : (
|
||||
"Pilih tanggal"
|
||||
)}
|
||||
</TextCustom>
|
||||
</Pressable>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={1} style={{ alignItems: "center" }}>
|
||||
<TextCustom color="gray">|</TextCustom>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={5} style={{}}>
|
||||
<Pressable onPress={toggleTimePicker}>
|
||||
<TextCustom color="gray">
|
||||
{selectedTime ? (
|
||||
<TextCustom color="black">
|
||||
{selectedTime.toLocaleTimeString("id-ID", {
|
||||
minute: "2-digit",
|
||||
hour: "2-digit",
|
||||
})}
|
||||
</TextCustom>
|
||||
) : (
|
||||
"Pilih waktu"
|
||||
)}
|
||||
</TextCustom>
|
||||
</Pressable>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</View>
|
||||
{externalError ||
|
||||
(internalError && (
|
||||
<Text style={GStyles.inputErrorMessage}>
|
||||
{externalError || internalError}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{showDate && (
|
||||
<DateTimePicker
|
||||
testID="dateTimePicker"
|
||||
value={selectedDate || new Date()}
|
||||
mode="date"
|
||||
is24Hour={true}
|
||||
display="default"
|
||||
onChange={handleConfirmDate}
|
||||
minimumDate={minimumDate}
|
||||
maximumDate={maximumDate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTime && (
|
||||
<DateTimePicker
|
||||
testID="dateTimePicker"
|
||||
value={selectedTime || new Date()}
|
||||
mode="time"
|
||||
is24Hour={true}
|
||||
display="default"
|
||||
onChange={handleConfirmTime}
|
||||
minimumDate={minimumDate}
|
||||
maximumDate={maximumDate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateTimeInput_Android;
|
||||
161
components/DateInput/DateTimeIOS.tsx
Normal file
161
components/DateInput/DateTimeIOS.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
// DateTimeInput.tsx
|
||||
import { MainColor } from "@/constants/color-palet";
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import DateTimePicker, {
|
||||
DateTimePickerEvent,
|
||||
} from "@react-native-community/datetimepicker";
|
||||
import dayjs from "dayjs";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
StyleProp,
|
||||
Text,
|
||||
View,
|
||||
ViewStyle
|
||||
} from "react-native";
|
||||
import ClickableCustom from "../Clickable/ClickableCustom";
|
||||
import TextCustom from "../Text/TextCustom";
|
||||
|
||||
interface DateTimeInputProps {
|
||||
// Main
|
||||
value?: DateTimePickerEvent;
|
||||
mode?: "date" | "time";
|
||||
onChange: (selectedDate: DateTimePickerEvent) => void;
|
||||
maximumDate?: Date;
|
||||
minimumDate?: Date;
|
||||
// Main
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
iconLeft?: React.ReactNode;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
borderRadius?: number;
|
||||
externalError?: string;
|
||||
internalError?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const DateTimeInput_IOS: React.FC<DateTimeInputProps> = ({
|
||||
// Main
|
||||
value,
|
||||
mode,
|
||||
onChange,
|
||||
maximumDate,
|
||||
minimumDate,
|
||||
// Main
|
||||
label,
|
||||
required,
|
||||
disabled,
|
||||
iconLeft,
|
||||
style,
|
||||
borderRadius = 8,
|
||||
externalError,
|
||||
internalError,
|
||||
containerStyle,
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(
|
||||
value as any
|
||||
);
|
||||
|
||||
const handleConfirm = (event: any, date?: Date) => {
|
||||
if (event.type === "set" && date !== undefined) {
|
||||
setSelectedDate(date);
|
||||
onChange(date as any);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = () => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ClickableCustom
|
||||
activeOpacity={0.8}
|
||||
style={[GStyles.inputContainerArea, containerStyle]}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{label && (
|
||||
<Text style={GStyles.inputLabel}>
|
||||
{label}
|
||||
{required && <Text style={GStyles.inputRequired}> *</Text>}
|
||||
</Text>
|
||||
)}
|
||||
<View
|
||||
style={[
|
||||
style,
|
||||
{ borderRadius },
|
||||
externalError || internalError ? GStyles.inputErrorBorder : null,
|
||||
GStyles.inputContainerInput,
|
||||
disabled && GStyles.disabledBox,
|
||||
]}
|
||||
>
|
||||
<View style={GStyles.inputIcon}>
|
||||
<Ionicons
|
||||
name="calendar-outline"
|
||||
size={20}
|
||||
color={MainColor.placeholder}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<TextCustom color="gray">
|
||||
{selectedDate ? (
|
||||
<TextCustom color="black">
|
||||
{dayjs(selectedDate).format("DD-MM-YYYY HH:mm")}
|
||||
</TextCustom>
|
||||
) : (
|
||||
"Pilih tanggal"
|
||||
)}
|
||||
</TextCustom>
|
||||
</View>
|
||||
{externalError ||
|
||||
(internalError && (
|
||||
<Text style={GStyles.inputErrorMessage}>
|
||||
{externalError || internalError}
|
||||
</Text>
|
||||
))}
|
||||
</ClickableCustom>
|
||||
|
||||
{show && (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 15,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 8,
|
||||
padding: 10,
|
||||
// top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
borderColor: "#ccc",
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<View style={{ alignItems: "flex-end" }}>
|
||||
<Ionicons
|
||||
name="close"
|
||||
size={20}
|
||||
color="black"
|
||||
onPress={() => setShow(false)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<DateTimePicker
|
||||
value={selectedDate || new Date()}
|
||||
mode={"datetime"}
|
||||
display="inline"
|
||||
onChange={handleConfirm}
|
||||
minimumDate={minimumDate}
|
||||
maximumDate={maximumDate}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateTimeInput_IOS;
|
||||
54
components/DateInput/DateTimePickerCustom.tsx
Normal file
54
components/DateInput/DateTimePickerCustom.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
import {
|
||||
DateTimePickerEvent,
|
||||
} from "@react-native-community/datetimepicker";
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import DateTimeInput_Android from "./DataTimeAndroid";
|
||||
import DateTimeInput_IOS from "./DateTimeIOS";
|
||||
|
||||
type Props = {
|
||||
value?: Date;
|
||||
onChange?: (date: Date) => void;
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
maximumDate?: Date;
|
||||
minimumDate?: Date;
|
||||
};
|
||||
|
||||
const DateTimePickerCustom: React.FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
required,
|
||||
maximumDate,
|
||||
minimumDate,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{Platform.OS === "ios" ? (
|
||||
<DateTimeInput_IOS
|
||||
label={label}
|
||||
onChange={(date: DateTimePickerEvent) => {
|
||||
onChange?.(date as any);
|
||||
}}
|
||||
required={required}
|
||||
maximumDate={maximumDate}
|
||||
minimumDate={minimumDate}
|
||||
/>
|
||||
) : (
|
||||
<DateTimeInput_Android
|
||||
label={label}
|
||||
onChange={(date: DateTimePickerEvent) => {
|
||||
onChange?.(date as any);
|
||||
}}
|
||||
required={required}
|
||||
maximumDate={maximumDate}
|
||||
minimumDate={minimumDate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateTimePickerCustom;
|
||||
70
components/DateInput/DateTimeTry.tsx
Normal file
70
components/DateInput/DateTimeTry.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useState } from "react";
|
||||
import { Pressable, Text, StyleSheet } from "react-native";
|
||||
import DateTimePicker, { Event } from "@react-native-community/datetimepicker";
|
||||
|
||||
type Props = {
|
||||
value?: Date;
|
||||
mode?: "date" | "time" | "datetime";
|
||||
onChange: (date: Date) => void;
|
||||
};
|
||||
|
||||
const DateTimePickerTry: React.FC<Props> = ({
|
||||
value = new Date(),
|
||||
mode = "date",
|
||||
onChange,
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const toggleDatePicker = () => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
||||
const handleConfirm = (event: Event, selectedDate?: Date) => {
|
||||
if (event.type === "set" && selectedDate !== undefined) {
|
||||
onChange(selectedDate);
|
||||
}
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable onPress={toggleDatePicker} style={styles.button}>
|
||||
<Text style={styles.buttonText}>
|
||||
{value ? value.toLocaleDateString() : "Pilih tanggal"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
{show && (
|
||||
<DateTimePicker
|
||||
// style={styles.button}
|
||||
textColor="white"
|
||||
testID="dateTimePicker"
|
||||
value={value}
|
||||
mode={mode}
|
||||
is24Hour={true}
|
||||
display="default"
|
||||
onChange={handleConfirm as any}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 8,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginVertical: 10,
|
||||
},
|
||||
buttonText: {
|
||||
color: "white",
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
});
|
||||
|
||||
export default DateTimePickerTry;
|
||||
25
components/Divider/Divider.tsx
Normal file
25
components/Divider/Divider.tsx
Normal 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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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}%` }]}
|
||||
|
||||
193
components/Progress/ProgressCustom.tsx
Normal file
193
components/Progress/ProgressCustom.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
132
components/Radio/RadioCustom.tsx
Normal file
132
components/Radio/RadioCustom.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
// components/Radio.tsx
|
||||
import { GStyles } from '@/styles/global-styles';
|
||||
import React, { createContext, useCallback, useContext } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
|
||||
// ========================
|
||||
// Types
|
||||
// ========================
|
||||
|
||||
type RadioContextType = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const RadioContext = createContext<RadioContextType | undefined>(undefined);
|
||||
|
||||
interface RadioGroupProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
children: React.ReactNode;
|
||||
style?: ViewStyle;
|
||||
}
|
||||
|
||||
interface RadioProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
disabled?: boolean;
|
||||
style?: ViewStyle;
|
||||
labelStyle?: TextStyle;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Components
|
||||
// ========================
|
||||
|
||||
export const RadioGroup: React.FC<RadioGroupProps> = ({ value, onChange, children, style }) => {
|
||||
const contextValue = {
|
||||
value,
|
||||
onChange,
|
||||
};
|
||||
|
||||
return <RadioContext.Provider value={contextValue}>{children}</RadioContext.Provider>;
|
||||
};
|
||||
|
||||
export const RadioCustom: React.FC<RadioProps> = ({ label, value, disabled = false, style, labelStyle }) => {
|
||||
const context = useContext(RadioContext);
|
||||
if (!context) {
|
||||
throw new Error('Radio must be used inside a RadioGroup');
|
||||
}
|
||||
|
||||
const { value: selectedValue, onChange } = context;
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
if (!disabled && selectedValue !== value) {
|
||||
onChange(value as any);
|
||||
}
|
||||
}, [disabled, selectedValue, value, onChange]);
|
||||
|
||||
const isSelected = selectedValue === value;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.radioContainer, style]}
|
||||
onPress={handlePress}
|
||||
disabled={disabled}
|
||||
>
|
||||
{/* Circle */}
|
||||
<View
|
||||
style={[styles.outerCircle, isSelected && styles.outerCircleChecked]}
|
||||
>
|
||||
<View
|
||||
style={[styles.innerCircle, isSelected && styles.innerCircleChecked]}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Label */}
|
||||
<Text
|
||||
style={[
|
||||
GStyles.textLabelBold,
|
||||
labelStyle,
|
||||
disabled && GStyles.inputTextDisabled,
|
||||
]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
// ========================
|
||||
// Styles
|
||||
// ========================
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
radioContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
},
|
||||
outerCircle: {
|
||||
height: 24,
|
||||
width: 24,
|
||||
borderRadius: 12,
|
||||
borderWidth: 2,
|
||||
borderColor: '#3B82F6',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 10,
|
||||
},
|
||||
outerCircleChecked: {
|
||||
backgroundColor: '#3B82F6',
|
||||
},
|
||||
innerCircle: {
|
||||
height: 12,
|
||||
width: 12,
|
||||
borderRadius: 6,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
innerCircleChecked: {
|
||||
backgroundColor: 'white',
|
||||
borderColor: 'white',
|
||||
borderRadius: 6,
|
||||
},
|
||||
|
||||
});
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,8 +22,8 @@ interface TextCustomProps {
|
||||
style?: StyleProp<TextStyle>;
|
||||
bold?: boolean;
|
||||
semiBold?: boolean;
|
||||
size?: "default" | "large" | "small";
|
||||
color?: "default" | "yellow" | "red" | "gray" | "green";
|
||||
size?: "default" | "large" | "small" | "xlarge";
|
||||
color?: "default" | "yellow" | "red" | "gray" | "green" | "black"
|
||||
align?: TextAlign; // Prop untuk alignment
|
||||
truncate?: boolean | number;
|
||||
onPress?: () => void;
|
||||
@@ -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
|
||||
@@ -58,6 +60,7 @@ const TextCustom: React.FC<TextCustomProps> = ({
|
||||
else if (color === "red") selectedStyles.push(styles.red);
|
||||
else if (color === "gray") selectedStyles.push(styles.gray);
|
||||
else if (color === "green") selectedStyles.push(styles.green);
|
||||
else if (color === "black") selectedStyles.push(styles.black);
|
||||
|
||||
// Alignment
|
||||
if (align) {
|
||||
@@ -112,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,
|
||||
@@ -130,4 +136,7 @@ export const styles = StyleSheet.create({
|
||||
green: {
|
||||
color: MainColor.green,
|
||||
},
|
||||
black: {
|
||||
color: MainColor.black,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { GStyles } from "@/styles/global-styles";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
TextInput as RNTextInput,
|
||||
StyleProp,
|
||||
Text,
|
||||
View,
|
||||
ViewStyle,
|
||||
TextInput as RNTextInput,
|
||||
StyleProp,
|
||||
Text,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
|
||||
type IconType = React.ReactNode | string;
|
||||
@@ -79,7 +79,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={GStyles.inputContainerArea}>
|
||||
<View style={[GStyles.inputContainerArea]}>
|
||||
{label && (
|
||||
<Text style={GStyles.inputLabel}>
|
||||
{label}
|
||||
@@ -105,7 +105,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
|
||||
multiline
|
||||
numberOfLines={numberOfLines}
|
||||
style={[
|
||||
GStyles.inputText,
|
||||
// GStyles.inputText,
|
||||
GStyles.textAreaInput,
|
||||
{ color: fontColor },
|
||||
]}
|
||||
|
||||
13
components/_Icon/IconArchive.tsx
Normal file
13
components/_Icon/IconArchive.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
14
components/_Icon/IconContribution.tsx
Normal file
14
components/_Icon/IconContribution.tsx
Normal 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"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
10
components/_Icon/IconEdit.tsx
Normal file
10
components/_Icon/IconEdit.tsx
Normal 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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
components/_Icon/IconHistory.tsx
Normal file
9
components/_Icon/IconHistory.tsx
Normal 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
10
components/_Icon/IconHome.tsx
Normal file
10
components/_Icon/IconHome.tsx
Normal 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"} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user