Compare commits

...

9 Commits

Author SHA1 Message Date
c9a1ac1db5 Voting
Add:
- BoxDetailContribution
- app/(application)/(user)/voting/[id]/contribution.tsx

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

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

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

Fix:
- BoxPublishSection
- ReportListSection: Hapus import useless

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

Voting
Fix:
- history

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

Voting
Add:
- screens/Voting

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

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

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

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

Job
Add:
- edit & status per id

- BoxDetailSectio
- ButtonStatusSection

Fix:
- index, status, archive: penyesuaian ui

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

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

Package:
Add: expo-clipboard

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

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

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

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

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

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

Home
Fix:
- Penambahan onPres ke job

Component
Add:
- Icon: home, status

# No Issue
2025-07-25 10:53:31 +08:00
53 changed files with 2286 additions and 103 deletions

View File

@@ -150,6 +150,70 @@ export default function UserLayout() {
{/* ========== 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 ========= */}
{/* ========== 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"

View File

@@ -1,3 +1,4 @@
import { IconHome } from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
@@ -9,9 +10,7 @@ export default function CollaborationTabsLayout() {
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="home" color={color} />
),
tabBarIcon: ({ color }) => <IconHome color={color} />,
}}
/>
<Tabs.Screen

View File

@@ -1,49 +1,43 @@
import {
IconContribution,
IconHistory,
IconHome,
IconStatus,
} from "@/components/_Icon";
import { TabsStyles } from "@/styles/tabs-styles";
import { FontAwesome5, Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
export default function EventTabsLayout() {
return (
<Tabs
screenOptions={TabsStyles}
>
<Tabs screenOptions={TabsStyles}>
<Tabs.Screen
name="index"
options={{
title: "Beranda",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="home" color={color} />
),
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="contribution"
options={{
title: "Kontribusi",
tabBarIcon: ({ color }) => (
<Ionicons size={20} name="extension-puzzle" color={color} />
),
tabBarIcon: ({ color }) => <IconContribution color={color} />,
}}
/>
<Tabs.Screen
name="history"
options={{
title: "Riwayat",
tabBarIcon: ({ color }) => (
<FontAwesome5 size={20} name="history" color={color} />
),
tabBarIcon: ({ color }) => <IconHistory color={color} />,
}}
/>
</Tabs>
);
}

View File

@@ -73,9 +73,9 @@ export default function EventDetailStatus() {
height={250}
>
<MenuDrawerDynamicGrid
data={menuDrawerDraftEvent({ id: id as string })}
data={menuDrawerDraftEvent({ id: id as string }) as any}
columns={4}
onPressItem={handlePress}
onPressItem={handlePress as any}
/>
</DrawerCustom>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,67 @@
import {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
export default function JobEdit() {
const buttonSubmit = () => {
return (
<>
<ButtonCustom onPress={() => router.back()}>Update</ButtonCustom>
<Spacing />
</>
);
};
return (
<ViewWrapper>
<StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
{buttonSubmit()}
</StackCustom>
</ViewWrapper>
);
}

View File

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

View File

@@ -0,0 +1,73 @@
import {
ButtonCenteredOnly,
ButtonCustom,
InformationBox,
LandscapeFrameUploaded,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
export default function JobCreate() {
const buttonSubmit = () => {
return (
<>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/job/(tabs)/status")
}
>
Simpan
</ButtonCustom>
<Spacing />
</>
);
};
return (
<ViewWrapper>
<StackCustom gap={"xs"}>
<InformationBox text="Poster atau gambar lowongan kerja bersifat opsional, tidak wajib untuk dimasukkan dan upload lah gambar yang sesuai dengan deskripsi lowongan kerja." />
<LandscapeFrameUploaded />
<ButtonCenteredOnly
onPress={() => {
router.push("/(application)/(image)/take-picture/123");
}}
icon="upload"
>
Upload
</ButtonCenteredOnly>
<Spacing />
<TextInputCustom
label="Judul Lowongan"
placeholder="Masukan Judul Lowongan Kerja"
required
/>
<TextAreaCustom
label="Syarat & Kualifikasi"
placeholder="Masukan Syarat & Kualifikasi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
<TextAreaCustom
label="Deskripsi Lowongan"
placeholder="Masukan Deskripsi Lowongan Kerja"
required
showCount
maxLength={1000}
/>
{buttonSubmit()}
</StackCustom>
</ViewWrapper>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
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} />
))}
</ViewWrapper>
);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { TouchableOpacity } from "react-native";
export default function VotingEdit() {
const buttonSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.back()
}
>
Update
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
/>
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
<Grid>
<Grid.Col span={10}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
</StackCustom>
</ViewWrapper>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { TouchableOpacity } from "react-native";
export default function VotingCreate() {
const buttonSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/voting/(tabs)/status")
}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return (
<ViewWrapper footerComponent={buttonSubmit()}>
<StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
/>
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
<Grid>
<Grid.Col span={10}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -16,6 +16,7 @@
"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-font": "~13.3.2",
"expo-haptics": "~14.1.4",
@@ -35,6 +36,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",
@@ -827,6 +829,8 @@
"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-file-system": ["expo-file-system@18.1.11", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-HJw/m0nVOKeqeRjPjGdvm+zBi5/NxcdPf8M8P3G2JFvH5Z8vBWqVDic2O58jnT1OFEy0XXzoH9UqFu7cHg9DTQ=="],
@@ -1389,6 +1393,8 @@
"react-native-otp-entry": ["react-native-otp-entry@1.8.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TZNkIuUzZKAAWrC8X/A22ZHJdycLysxUNysrGf0yTmDLRUyf4zLXwVFcDYUcRNe763Hjaf5qvtKGILb6lDGzoA=="],
"react-native-pager-view": ["react-native-pager-view@6.7.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw=="],
"react-native-paper": ["react-native-paper@5.14.5", "", { "dependencies": { "@callstack/react-theme-provider": "^3.0.9", "color": "^3.1.2", "use-latest-callback": "^0.2.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-safe-area-context": "*" } }, "sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ=="],
"react-native-reanimated": ["react-native-reanimated@3.17.5", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4", "react-native-is-edge-to-edge": "1.1.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw=="],

View File

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

View File

@@ -72,6 +72,8 @@ export default function BaseBox({
marginBottom,
paddingBlock,
paddingInline,
paddingTop,
paddingBottom,
},
style,
]}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
import { MainColor, AccentColor } from "@/constants/color-palet";
import { View } from "react-native";
import ButtonCustom from "../Button/ButtonCustom";
import Spacing from "./Spacing";
export default function TabsTwoHeaderCustom ({
leftValue,
rightValue,
leftText,
rightText,
activeCategory,
handlePress,
}: {
leftValue: string;
rightValue: string;
leftText: string;
rightText: string;
activeCategory: string | null;
handlePress: (item: string) => void;
}) {
return (
<>
<View
style={{
flexDirection: "row",
alignItems: "center",
padding: 5,
backgroundColor: MainColor.soft_darkblue,
borderRadius: 50,
width: "100%",
}}
>
<ButtonCustom
backgroundColor={
activeCategory === leftValue ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === leftValue ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress(leftValue)}
>
{leftText}
</ButtonCustom>
<Spacing width={"2%"} />
<ButtonCustom
backgroundColor={
activeCategory === rightValue ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === rightValue ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress(rightValue)}
>
{rightText}
</ButtonCustom>
</View>
</>
);
}

View File

@@ -7,6 +7,10 @@ import ButtonCenteredOnly from "./Button/ButtonCenteredOnly";
import ButtonCustom from "./Button/ButtonCustom";
import DotButton from "./Button/DotButton";
import FloatingButton from "./Button/FloatingButton";
// Badge
import BadgeCustom from "./Badge/BadgeCustom";
// Container
import CircleContainer from "./Container/CircleContainer";
// Checkbox
import CheckboxCustom from "./Checkbox/CheckboxCustom";
import CheckboxGroup from "./Checkbox/CheckboxGroup";
@@ -49,6 +53,8 @@ import AvatarUsernameAndOtherComponent from "./_ShareComponent/AvataraAndOtherHe
import Spacing from "./_ShareComponent/Spacing";
import TabBarBackground from "./_ShareComponent/TabBarBackground";
import ViewWrapper from "./_ShareComponent/ViewWrapper";
import SearchInput from "./_ShareComponent/SearchInput";
import DummyLandscapeImage from "./_ShareComponent/DummyLandscapeImage";
export {
AlertCustom,
@@ -66,6 +72,8 @@ export {
ButtonCenteredOnly,
ButtonCustom,
DotButton,
// Badge
BadgeCustom,
// Center
CenterCustom,
// Checkbox
@@ -73,6 +81,8 @@ export {
CheckboxGroup,
// Clickable
ClickableCustom,
// Container
CircleContainer,
// Divider
Divider,
DividerCustom,
@@ -91,6 +101,8 @@ export {
// Select
SelectCustom,
// ShareComponent
SearchInput,
DummyLandscapeImage,
Spacing,
// Stack
StackCustom,

View File

@@ -23,6 +23,7 @@
"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-font": "~13.3.2",
"expo-haptics": "~14.1.4",
@@ -42,6 +43,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",

View File

@@ -1,8 +1,6 @@
import { BaseBox, ButtonCustom, StackCustom, TextCustom } from "@/components";
import { BaseBox, StackCustom, TextCustom } from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { MainColor } from "@/constants/color-palet";
import { listDummyReportForum } from "@/lib/dummy-data/forum/report-list";
import { router } from "expo-router";
import { useState } from "react";
import { View } from "react-native";

View File

@@ -1,52 +1,55 @@
import { TextCustom } from "@/components";
import { ClickableCustom, TextCustom } from "@/components";
import Spacing from "@/components/_ShareComponent/Spacing";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";
import { stylesHome } from "./homeViewStyle";
import { router } from "expo-router";
export default function Home_BottomFeatureSection() {
return (
<>
<View style={stylesHome.jobVacancyContainer}>
<View style={stylesHome.jobVacancyHeader}>
<Icon name="briefcase" size={24} color="white" />
<Spacing width={10}/>
<TextCustom bold size="large">
Job Vacancy
</TextCustom>
</View>
<View style={stylesHome.vacancyList}>
{/* Vacancy Item 1 */}
<View style={stylesHome.vacancyItem}>
{/* <Icon name="user" size={20} color="#FFD700" /> */}
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
Bagas_banuna
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>
Dicari perawat kucing dan perawat anjing
</TextCustom>
</View>
<ClickableCustom onPress={() => router.push("/job")}>
<View style={stylesHome.jobVacancyContainer}>
<View style={stylesHome.jobVacancyHeader}>
<Icon name="briefcase" size={24} color="white" />
<Spacing width={10} />
<TextCustom bold size="large">
Job Vacancy
</TextCustom>
</View>
{/* Vacancy Item 2 */}
<View style={stylesHome.vacancyItem}>
{/* <Icon name="user" size={20} color="#FFD700" /> */}
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
fibramarcell
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>
Di Butuhkan Seorang Programer dan Designer
</TextCustom>
<View style={stylesHome.vacancyList}>
{/* Vacancy Item 1 */}
<View style={stylesHome.vacancyItem}>
{/* <Icon name="user" size={20} color="#FFD700" /> */}
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
Bagas_banuna
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>
Dicari perawat kucing dan perawat anjing
</TextCustom>
</View>
</View>
{/* Vacancy Item 2 */}
<View style={stylesHome.vacancyItem}>
{/* <Icon name="user" size={20} color="#FFD700" /> */}
<View style={stylesHome.vacancyDetails}>
<TextCustom bold color="yellow" truncate size="large">
fibramarcell
</TextCustom>
<Spacing height={5} />
<TextCustom truncate={2}>
Di Butuhkan Seorang Programer dan Designer
</TextCustom>
</View>
</View>
</View>
</View>
</View>
</ClickableCustom>
</>
);
}

View File

@@ -16,12 +16,17 @@ export default function Home_FeatureSection() {
</TouchableOpacity>
<TouchableOpacity
style={stylesHome.gridItem}
onPress={() => router.push("/(application)/(user)/collaboration/(tabs)")}
onPress={() =>
router.push("/(application)/(user)/collaboration/(tabs)")
}
>
<Ionicons name="share" size={48} color="white" />
<Text style={stylesHome.gridLabel}>Collaboration</Text>
</TouchableOpacity>
<TouchableOpacity style={stylesHome.gridItem}>
<TouchableOpacity
style={stylesHome.gridItem}
onPress={() => router.push("/(application)/(user)/voting/(tabs)")}
>
<Ionicons name="cube" size={48} color="white" />
<Text style={stylesHome.gridLabel}>Voting</Text>
</TouchableOpacity>

View File

@@ -0,0 +1,28 @@
import { BaseBox, StackCustom, DummyLandscapeImage, TextCustom } from "@/components";
export default function Job_BoxDetailSection({data}: {data: any}) {
return (
<>
<BaseBox>
<StackCustom gap={"lg"}>
<DummyLandscapeImage />
<TextCustom align="center" bold size="large">
{data?.posisi}
</TextCustom>
<StackCustom gap={"sm"}>
<TextCustom bold>Syarat & Ketentuan :</TextCustom>
<TextCustom>{data?.syaratKetentuan}</TextCustom>
</StackCustom>
<StackCustom gap={"sm"}>
<TextCustom bold>Deskripsi :</TextCustom>
<TextCustom>{data?.deskripsi}</TextCustom>
</StackCustom>
</StackCustom>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,135 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { router } from "expo-router";
import { View } from "react-native";
export default function Job_ButtonStatusSection({
status,
}: {
status: string;
}) {
const handleBatalkanReview = () => {
AlertDefaultSystem({
title: "Batalkan Review",
message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const handleAjukanReview = () => {
AlertDefaultSystem({
title: "Ajukan Review",
message: "Apakah Anda yakin ingin ajukan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const handleEditKembali = () => {
AlertDefaultSystem({
title: "Edit Kembali",
message: "Apakah Anda yakin ingin edit kembali ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const handleOpenDeleteAlert = () => {
AlertDefaultSystem({
title: "Hapus",
message: "Apakah Anda yakin ingin menghapus data ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const DeleteButton = () => {
return (
<>
<ButtonCustom
backgroundColor="red"
textColor="white"
onPress={handleOpenDeleteAlert}
>
Hapus
</ButtonCustom>
</>
);
};
switch (status) {
case "publish":
return (
<>
<ButtonCustom
onPress={() => {
console.log("Arsipkan");
router.replace("/(application)/(user)/job/(tabs)/archive");
}}
>
Arsipkan
</ButtonCustom>
</>
);
case "review":
return (
<ButtonCustom onPress={handleBatalkanReview}>
Batalkan Review
</ButtonCustom>
);
case "draft":
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleAjukanReview}>
Ajukan Review
</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
case "reject":
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleEditKembali}>
Edit Kembali
</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
default:
return <ButtonCustom disabled>Status Undifined</ButtonCustom>;
}
}

View File

@@ -0,0 +1,82 @@
export const jobDataDummy = [
{
id: 1,
posisi: "Front-End Developer",
deskripsi:
"Kami mencari Front-End Developer yang berpengalaman untuk membangun dan mengembangkan antarmuka pengguna web yang interaktif, responsif, dan menarik menggunakan React.js dan TypeScript. Kandidat akan bekerja sama dengan tim desain dan back-end untuk memastikan pengalaman pengguna yang optimal di berbagai perangkat.",
syaratKetentuan:
"Minimal 2 tahun pengalaman sebagai Front-End Developer. Menguasai React.js, TypeScript, HTML, CSS (SCSS). Memahami prinsip-prinsip desain responsif dan pengalaman pengguna (UX). Pengalaman dengan Git dan CI/CD pipeline menjadi nilai tambah.",
},
{
id: 2,
posisi: "Back-End Developer",
deskripsi:
"Posisi ini bertanggung jawab dalam membangun dan memelihara RESTful API, serta mengelola logika server dan interaksi dengan basis data. Anda akan bekerja dengan Node.js dan Express untuk mendukung berbagai kebutuhan aplikasi kami yang dinamis dan real-time.",
syaratKetentuan:
"Pengalaman minimal 2 tahun sebagai Back-End Developer. Menguasai Node.js, Express.js, dan basis data (MongoDB, PostgreSQL). Paham prinsip REST API, middleware, dan keamanan data. Pengalaman dengan Docker dan testing (Jest/Mocha) menjadi nilai plus.",
},
{
id: 3,
posisi: "UI/UX Designer",
deskripsi:
"Kami mencari desainer kreatif untuk merancang pengalaman pengguna dan antarmuka yang modern dan intuitif. Anda akan terlibat dalam proses riset pengguna, wireframing, prototyping, dan pengujian desain untuk aplikasi web dan mobile.",
syaratKetentuan:
"Minimal 1 tahun pengalaman dalam UI/UX. Menguasai alat desain seperti Figma, Sketch, atau Adobe XD. Mampu membuat prototipe interaktif dan melakukan usability testing. Portofolio desain UI/UX yang pernah dikerjakan wajib dilampirkan.",
},
{
id: 4,
posisi: "DevOps Engineer",
deskripsi:
"Sebagai DevOps Engineer, Anda akan bertanggung jawab atas deployment otomatis, pemantauan sistem, dan infrastruktur cloud kami. Posisi ini akan fokus pada integrasi dan pengiriman berkelanjutan (CI/CD), skalabilitas, serta keamanan sistem.",
syaratKetentuan:
"Menguasai Docker, Kubernetes, Jenkins, GitLab CI, dan monitoring tools (Prometheus, Grafana). Memiliki pengalaman kerja dengan platform cloud seperti AWS atau GCP. Familiar dengan scripting Bash/Python dan prinsip keamanan DevOps.",
},
{
id: 5,
posisi: "Mobile Developer",
deskripsi:
"Kami sedang mencari developer mobile yang terampil menggunakan Flutter untuk mengembangkan aplikasi lintas platform (iOS & Android) yang efisien dan user-friendly. Anda akan bekerja dalam tim produk untuk merancang dan mengimplementasikan fitur baru.",
syaratKetentuan:
"Pengalaman mengembangkan aplikasi Flutter setidaknya 1 tahun. Memahami state management (Provider, BLoC, atau Riverpod). Pernah merilis aplikasi di Google Play Store atau App Store. Pengalaman dengan Firebase menjadi nilai tambah.",
},
{
id: 6,
posisi: "Data Analyst",
deskripsi:
"Sebagai Data Analyst, Anda akan mengumpulkan, menganalisis, dan menginterpretasikan data untuk mendukung keputusan bisnis. Anda akan membuat dashboard interaktif, laporan rutin, dan memberikan insight yang bisa ditindaklanjuti kepada tim manajemen.",
syaratKetentuan:
"Pengalaman menggunakan SQL, Excel, dan tools visualisasi data seperti Power BI atau Tableau. Kemampuan analisis statistik dan storytelling data. Pengalaman dengan Python (pandas, numpy) merupakan keunggulan.",
},
{
id: 7,
posisi: "Machine Learning Engineer",
deskripsi:
"Kami membuka posisi untuk insinyur Machine Learning yang akan mengembangkan dan mengimplementasikan model prediktif untuk mendukung berbagai kebutuhan bisnis. Anda akan bekerja dengan data dalam skala besar dan menerapkan teknik NLP, klasifikasi, dan rekomendasi.",
syaratKetentuan:
"Memiliki pengalaman minimal 1 tahun dalam ML project. Menguasai Python, scikit-learn, TensorFlow, atau PyTorch. Mampu memproses data besar dengan pandas, NumPy, dan Spark. Familiar dengan deployment model ML ke production.",
},
{
id: 8,
posisi: "System Administrator",
deskripsi:
"Posisi ini bertanggung jawab atas pengelolaan dan pemeliharaan sistem server Linux, jaringan internal, dan keamanan TI perusahaan. Termasuk setup server, monitoring, dan troubleshooting sistem operasional harian.",
syaratKetentuan:
"Berpengalaman mengelola server Linux (Ubuntu/CentOS), firewall, dan konfigurasi jaringan. Paham konsep load balancing, backup otomatis, dan disaster recovery. Kemampuan scripting (Bash, Python) menjadi nilai tambah.",
},
{
id: 9,
posisi: "Quality Assurance",
deskripsi:
"Kami mencari QA Engineer untuk memastikan kualitas produk melalui pengujian manual maupun otomatis. Anda akan menulis test case, menjalankan regresi test, dan bekerja erat dengan tim developer untuk menemukan serta memperbaiki bug lebih awal.",
syaratKetentuan:
"Pengalaman dalam manual testing dan tools otomasi seperti Selenium, Cypress, atau Playwright. Memahami prinsip SDLC dan metodologi Agile. Mampu menulis dokumentasi QA dan membuat test report secara sistematis.",
},
{
id: 10,
posisi: "Product Manager",
deskripsi:
"Sebagai Product Manager, Anda akan memimpin pengembangan produk mulai dari perencanaan, peluncuran, hingga peningkatan. Anda akan mengelola roadmap, prioritas fitur, dan bekerja lintas fungsi dengan tim desain, dev, dan bisnis.",
syaratKetentuan:
"Minimal 2 tahun pengalaman sebagai Product Manager. Memahami Agile/Scrum. Keterampilan komunikasi dan analisis yang kuat. Pengalaman dalam tools seperti Jira, Confluence, dan alat wireframing. Kemampuan mengambil keputusan berbasis data.",
},
];

View File

@@ -0,0 +1,56 @@
import {
BadgeCustom,
BoxWithHeaderSection,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs";
import { View } from "react-native";
export function Voting_BoxDetailContributionSection({
headerAvatar,
}: {
headerAvatar?: React.ReactNode;
}) {
return (
<>
<BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />}
<StackCustom gap={"lg"}>
<TextCustom align="center" bold size="large">
Title of Voting Here
</TextCustom>
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Perspiciatis corporis blanditiis est provident corrupti facilis iste
cum voluptate. Natus eum aut quos consequatur doloribus fugiat sit
ullam minima non enim?
</TextCustom>
<View>
<TextCustom bold size="small" align="center">
Batas Voting
</TextCustom>
<BadgeCustom
style={[GStyles.alignSelfCenter, { width: "70%" }]}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
</View>
<StackCustom gap={"xs"}>
<TextCustom bold size="small" align="center">
Pilihan Anda
</TextCustom>
<BadgeCustom style={[GStyles.alignSelfCenter]}>
Pilihan 1
</BadgeCustom>
</StackCustom>
</StackCustom>
</BoxWithHeaderSection>
</>
);
}

View File

@@ -0,0 +1,30 @@
import {
BaseBox,
StackCustom,
TextCustom,
Grid,
CircleContainer,
} from "@/components";
export default function Voting_BoxDetailHasilVotingSection() {
return (
<>
<BaseBox>
<StackCustom>
<TextCustom bold align="center">
Hasil Voting
</TextCustom>
<Grid>
{Array.from({ length: 4 }).map((_, i) => (
<Grid.Col span={3} style={{ alignItems: "center" }} key={i}>
<CircleContainer value={9 % (i + 4)} />
<TextCustom size="small">Pilihan {i + 1}</TextCustom>
</Grid.Col>
))}
</Grid>
</StackCustom>
</BaseBox>
</>
);
}

View File

@@ -0,0 +1,71 @@
import {
BadgeCustom,
BoxWithHeaderSection,
ButtonCustom,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import { RadioCustom, RadioGroup } from "@/components/Radio/RadioCustom";
import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs";
import { useState } from "react";
import { View } from "react-native";
export function Voting_BoxDetailPublishSection({
headerAvatar,
}: {
headerAvatar?: React.ReactNode;
}) {
const [value, setValue] = useState<any | number>("");
return (
<>
<BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />}
<StackCustom gap={"lg"}>
<TextCustom align="center" bold size="large">
Title of Voting Here
</TextCustom>
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Perspiciatis corporis blanditiis est provident corrupti facilis iste
cum voluptate. Natus eum aut quos consequatur doloribus fugiat sit
ullam minima non enim?
</TextCustom>
<View>
<TextCustom bold size="small" align="center">
Batas Voting
</TextCustom>
<BadgeCustom
style={[GStyles.alignSelfCenter, { width: "70%" }]}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
</View>
<View>
<TextCustom bold size="small">
Pilihan :
</TextCustom>
<RadioGroup value={value} onChange={setValue}>
{Array.from({ length: 4 }).map((_, i) => (
<View key={i}>
<RadioCustom
label={`Pilihan ${i + 1}`}
value={`Pilihan ${i + 1}`}
/>
</View>
))}
</RadioGroup>
</View>
<ButtonCustom onPress={() => console.log("vote")}>
Vote
</ButtonCustom>
</StackCustom>
</BoxWithHeaderSection>
</>
);
}

View File

@@ -0,0 +1,59 @@
import {
BadgeCustom,
BoxWithHeaderSection,
Spacing,
StackCustom,
TextCustom,
} from "@/components";
import { GStyles } from "@/styles/global-styles";
import dayjs from "dayjs";
import { View } from "react-native";
export function Voting_BoxDetailSection({
headerAvatar,
}: {
headerAvatar?: React.ReactNode;
}) {
return (
<>
<BoxWithHeaderSection>
{headerAvatar ? headerAvatar : <Spacing />}
<StackCustom>
<TextCustom align="center" bold size="large">
Title of Voting Here
</TextCustom>
<TextCustom>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Perspiciatis corporis blanditiis est provident corrupti facilis iste
cum voluptate. Natus eum aut quos consequatur doloribus fugiat sit
ullam minima non enim?
</TextCustom>
<View>
<TextCustom bold size="small" align="center">
Batas Voting
</TextCustom>
<BadgeCustom
style={[GStyles.alignSelfCenter, { width: "70%" }]}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
</View>
<View>
<TextCustom bold size="small">
Pilihan :
</TextCustom>
{Array.from({ length: 3 }).map((_, i) => (
<View key={i}>
<TextCustom>Nama Pilihan {i + 1}</TextCustom>
<Spacing />
</View>
))}
</View>
</StackCustom>
</BoxWithHeaderSection>
</>
);
}

View File

@@ -0,0 +1,54 @@
import {
BoxWithHeaderSection,
AvatarUsernameAndOtherComponent,
Spacing,
StackCustom,
TextCustom,
BadgeCustom,
Grid,
CircleContainer,
} from "@/components";
import dayjs from "dayjs";
import { Href } from "expo-router";
export default function Voting_BoxPublishSection({
href,
id,
bottomComponent,
}: {
href?: Href
id?: string
bottomComponent?: React.ReactNode;
}) {
return (
<>
<BoxWithHeaderSection href={href}>
<AvatarUsernameAndOtherComponent avatarHref="/profile/1" />
<Spacing />
<StackCustom>
<TextCustom align="center" bold truncate size="large">
Voting Title {id}
</TextCustom>
<BadgeCustom
style={{ width: "70%", alignSelf: "center" }}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
<Grid>
{Array.from({ length: 4 }).map((_, i) => (
<Grid.Col span={3} style={{ alignItems: "center" }} key={i}>
<CircleContainer value={9 % (i + 4)} />
<TextCustom size="small">Pilihan {i + 1}</TextCustom>
</Grid.Col>
))}
</Grid>
{bottomComponent}
</StackCustom>
</BoxWithHeaderSection>
</>
);
}

View File

@@ -0,0 +1,124 @@
import { AlertDefaultSystem, ButtonCustom, Grid } from "@/components";
import { router } from "expo-router";
import { View } from "react-native";
export default function Voting_ButtonStatusSection({
status,
}: {
status: string;
}) {
const handleBatalkanReview = () => {
AlertDefaultSystem({
title: "Batalkan Review",
message: "Apakah Anda yakin ingin batalkan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const handleAjukanReview = () => {
AlertDefaultSystem({
title: "Ajukan Review",
message: "Apakah Anda yakin ingin ajukan review ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const handleEditKembali = () => {
AlertDefaultSystem({
title: "Edit Kembali",
message: "Apakah Anda yakin ingin edit kembali ini?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const handleOpenDeleteAlert = () => {
AlertDefaultSystem({
title: "Hapus",
message: "Apakah Anda yakin ingin menghapus data ini?",
textLeft: "Batal",
textRight: "Hapus",
onPressRight: () => {
console.log("Hapus");
router.back();
},
});
};
const DeleteButton = () => {
return (
<>
<ButtonCustom
backgroundColor="red"
textColor="white"
onPress={handleOpenDeleteAlert}
>
Hapus
</ButtonCustom>
</>
);
};
switch (status) {
case "publish":
return <></>;
case "review":
return (
<ButtonCustom onPress={handleBatalkanReview}>
Batalkan Review
</ButtonCustom>
);
case "draft":
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleAjukanReview}>
Ajukan Review
</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
case "reject":
return (
<>
<Grid>
<Grid.Col span={5}>
<ButtonCustom onPress={handleEditKembali}>
Edit Kembali
</ButtonCustom>
</Grid.Col>
<Grid.Col span={2}>
<View />
</Grid.Col>
<Grid.Col span={5}>{DeleteButton()}</Grid.Col>
</Grid>
</>
);
default:
return <ButtonCustom disabled>Status Undifined</ButtonCustom>;
}
}

View File

@@ -315,4 +315,9 @@ export const GStyles = StyleSheet.create({
},
// =============== TEXT INPUT , TEXT AREA , SELECT =============== //
// =============== Alignment =============== //
alignSelfCenter: {
alignSelf: "center",
},
});