From e02ae8e35d945ca766d80862c4dae449946df887 Mon Sep 17 00:00:00 2001 From: Bagasbanuna02 Date: Thu, 24 Jul 2025 16:47:16 +0800 Subject: [PATCH] Component Add : - CheckboxGroup - components/_Icon/ Fix: - CheckboxCustom: penambahan fitur untuk checkbox group Feature: Collaboration Add : - ProjectMainSelectedSection Fix : - detail-participant - detail-project-main # No Issue --- app/(application)/(user)/_layout.tsx | 5 +- .../collaboration/[id]/detail-participant.tsx | 4 +- .../[id]/detail-project-main.tsx | 125 ++++++++++-------- .../(user)/collaboration/[id]/edit.tsx | 53 ++++++++ components/Checkbox/CheckboxCustom.tsx | 72 ++++++---- components/Checkbox/CheckboxGroup.tsx | 75 +++++++++++ components/Checkbox/checkbox-styles.tsx | 3 +- components/_Icon/IconEdit.tsx | 10 ++ components/_Icon/index.ts | 3 + components/index.ts | 20 ++- .../ProjectMainSelectedSection.tsx | 69 ++++++++++ 11 files changed, 346 insertions(+), 93 deletions(-) create mode 100644 app/(application)/(user)/collaboration/[id]/edit.tsx create mode 100644 components/Checkbox/CheckboxGroup.tsx create mode 100644 components/_Icon/IconEdit.tsx create mode 100644 components/_Icon/index.ts create mode 100644 screens/Collaboration/ProjectMainSelectedSection.tsx diff --git a/app/(application)/(user)/_layout.tsx b/app/(application)/(user)/_layout.tsx index 461c287..5c87b0a 100644 --- a/app/(application)/(user)/_layout.tsx +++ b/app/(application)/(user)/_layout.tsx @@ -140,13 +140,14 @@ export default function UserLayout() { /> , }} /> + {/* ========== End Collaboration Section ========= */} {/* ========== Forum Section ========= */} diff --git a/app/(application)/(user)/collaboration/[id]/detail-participant.tsx b/app/(application)/(user)/collaboration/[id]/detail-participant.tsx index 549f6e9..5a11ddd 100644 --- a/app/(application)/(user)/collaboration/[id]/detail-participant.tsx +++ b/app/(application)/(user)/collaboration/[id]/detail-participant.tsx @@ -8,7 +8,7 @@ import { } from "@/components"; import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; -import { Feather, MaterialIcons } from "@expo/vector-icons"; +import { MaterialIcons } from "@expo/vector-icons"; import { useLocalSearchParams } from "expo-router"; import { useState } from "react"; @@ -21,7 +21,6 @@ export default function CollaborationDetailParticipant() { - Partisipan @@ -40,7 +39,6 @@ export default function CollaborationDetailParticipant() { } /> ))} - diff --git a/app/(application)/(user)/collaboration/[id]/detail-project-main.tsx b/app/(application)/(user)/collaboration/[id]/detail-project-main.tsx index 6a1acdc..32af517 100644 --- a/app/(application)/(user)/collaboration/[id]/detail-project-main.tsx +++ b/app/(application)/(user)/collaboration/[id]/detail-project-main.tsx @@ -1,78 +1,91 @@ import { - AvatarCustom, - AvatarUsernameAndOtherComponent, - BaseBox, + AlertDefaultSystem, + BackButton, + ButtonCustom, + DotButton, DrawerCustom, - Grid, + MenuDrawerDynamicGrid, + Spacing, StackCustom, TextCustom, - ViewWrapper, + ViewWrapper } from "@/components"; -import CheckboxCustom from "@/components/Checkbox/CheckboxCustom"; -import { ICON_SIZE_SMALL } from "@/constants/constans-value"; +import { IconEdit } from "@/components/_Icon"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; -import { Feather, MaterialIcons } from "@expo/vector-icons"; -import { useLocalSearchParams } from "expo-router"; +import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection"; +import { router, Stack, useLocalSearchParams } from "expo-router"; import { useState } from "react"; -import { View } from "react-native"; export default function CollaborationDetailProjectMain() { const { id } = useLocalSearchParams(); + const [openDrawer, setOpenDrawer] = useState(false); const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); - const [agreed, setAgreed] = useState(false); - const [newsletter, setNewsletter] = useState(false); + const [selected, setSelected] = useState<(string | number)[]>([]); + + const handleEdit = () => { + console.log("Edit collaboration"); + router.push("/(application)/(user)/collaboration/(id)/edit"); + }; return ( <> + , + headerRight: () => setOpenDrawer(true)} />, + }} + /> - - - - *{" "} - - Pilih user yang akan menjadi tim proyek anda - - + - {Array.from({ length: 5 }).map((_, index) => ( - - - { - console.log("newsletter", newsletter); - setNewsletter(!newsletter); - }} - /> - - - - - - - Username - - - - - - - ))} - - + { + 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 + + + setOpenDrawer(false)} + height={"auto"} + > + , + }, + ]} + onPressItem={(item) => { + handleEdit(); + }} + /> + + setOpenDrawerParticipant(false)} diff --git a/app/(application)/(user)/collaboration/[id]/edit.tsx b/app/(application)/(user)/collaboration/[id]/edit.tsx new file mode 100644 index 0000000..233bfe8 --- /dev/null +++ b/app/(application)/(user)/collaboration/[id]/edit.tsx @@ -0,0 +1,53 @@ +import { + ButtonCustom, + SelectCustom, + StackCustom, + TextAreaCustom, + TextInputCustom, + ViewWrapper, +} from "@/components"; +import { router } from "expo-router"; + +export default function CollaborationEdit() { + return ( + + + + + console.log(value)} + /> + + + + + + { + console.log("Update proyek"); + router.back(); + }} + /> + + + ); +} diff --git a/components/Checkbox/CheckboxCustom.tsx b/components/Checkbox/CheckboxCustom.tsx index 0e2090d..1c9a2f4 100644 --- a/components/Checkbox/CheckboxCustom.tsx +++ b/components/Checkbox/CheckboxCustom.tsx @@ -1,61 +1,82 @@ -import React, { useState } from "react"; -import { View, Text, TouchableOpacity, Animated, Easing } from "react-native"; +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(null); // Tipe props +// Tambahkan prop baru: groupValueKey interface CheckboxProps { label?: string; description?: string; error?: string; - value?: boolean; + value?: boolean; // controlled value (untuk standalone) onChange?: (checked: boolean) => void; disabled?: boolean; - size?: number; // ukuran checkbox (default: 20) - color?: string; // warna utama (default: '#3b82f6' - biru tailwind) + size?: number; + color?: string; style?: object; component?: React.ReactNode; + // Prop tambahan untuk Group + valueKey?: string | number; // nilai unik untuk identifikasi di group } -export const CheckboxCustom: React.FC = ({ +const CheckboxCustom: React.FC = ({ label, description, error, value: controlledValue, onChange, - disabled = false, + disabled: propDisabled, size = 20, - color = "#3b82f6", + color = AccentColor.softblue, style, component, + valueKey, }) => { - const [uncontrolledChecked, setUncontrolledChecked] = useState(false); - const isChecked = controlledValue ?? uncontrolledChecked; + // 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; - const newValue = !isChecked; - if (onChange) onChange(newValue); - if (controlledValue === undefined) { - setUncontrolledChecked(newValue); + if (isInsideGroup) { + const newValue = isChecked + ? group.value.filter((v) => v !== valueKey) + : [...group.value, valueKey!]; + group.onChange(newValue); + } else if (onChange) { + onChange(!controlledValue); } - - // Animasi scale - Animated.spring(scaleValue, { - toValue: newValue ? 1 : 0, - friction: 7, - tension: 40, - useNativeDriver: true, - }).start(); }; const styles = checkboxStyles({ size, color, - disabled, + disabled: disabled as boolean, error: !!error, }); @@ -106,4 +127,7 @@ export const CheckboxCustom: React.FC = ({ ); }; -export default CheckboxCustom +export default CheckboxCustom; + +// Export context agar bisa digunakan +export { CheckboxGroupContext }; diff --git a/components/Checkbox/CheckboxGroup.tsx b/components/Checkbox/CheckboxGroup.tsx new file mode 100644 index 0000000..3af0a64 --- /dev/null +++ b/components/Checkbox/CheckboxGroup.tsx @@ -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 = ({ + 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 ( + + + {label ? {label} : null} + {description ? ( + {description} + ) : null} + {children} + {error ? {error} : null} + + + ); +}; + +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; + diff --git a/components/Checkbox/checkbox-styles.tsx b/components/Checkbox/checkbox-styles.tsx index 01b5d93..7cc7afd 100644 --- a/components/Checkbox/checkbox-styles.tsx +++ b/components/Checkbox/checkbox-styles.tsx @@ -11,8 +11,7 @@ export const checkboxStyles = (props: { container: { flexDirection: "row", alignItems: "flex-start", - // marginBottom: 12, - + // marginBottom: 12, }, innerContainer: { flexDirection: "row", diff --git a/components/_Icon/IconEdit.tsx b/components/_Icon/IconEdit.tsx new file mode 100644 index 0000000..a370fb8 --- /dev/null +++ b/components/_Icon/IconEdit.tsx @@ -0,0 +1,10 @@ +import { ICON_SIZE_SMALL } from "@/constants/constans-value"; +import { FontAwesome5 } from "@expo/vector-icons"; + +export default function IconEdit() { + return ( + <> + + + ); +} diff --git a/components/_Icon/index.ts b/components/_Icon/index.ts new file mode 100644 index 0000000..728531e --- /dev/null +++ b/components/_Icon/index.ts @@ -0,0 +1,3 @@ +import IconEdit from "./IconEdit"; + +export { IconEdit }; diff --git a/components/index.ts b/components/index.ts index ea78045..42c5a40 100644 --- a/components/index.ts +++ b/components/index.ts @@ -7,6 +7,9 @@ import ButtonCenteredOnly from "./Button/ButtonCenteredOnly"; import ButtonCustom from "./Button/ButtonCustom"; import DotButton from "./Button/DotButton"; import FloatingButton from "./Button/FloatingButton"; +// Checkbox +import CheckboxCustom from "./Checkbox/CheckboxCustom"; +import CheckboxGroup from "./Checkbox/CheckboxGroup"; // Drawer import DrawerCustom from "./Drawer/DrawerCustom"; import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird"; @@ -59,18 +62,23 @@ export { // Box BaseBox, BoxButtonOnFooter, - BoxWithHeaderSection, ButtonCenteredOnly, + BoxWithHeaderSection, + ButtonCenteredOnly, ButtonCustom, DotButton, // Center CenterCustom, + // Checkbox + CheckboxCustom, + CheckboxGroup, // Clickable ClickableCustom, // Divider Divider, - DividerCustom, + DividerCustom, // Drawer - DrawerCustom, FloatingButton, + DrawerCustom, + FloatingButton, // Grid Grid, InformationBox, @@ -85,7 +93,8 @@ export { // ShareComponent Spacing, // Stack - StackCustom, TabBarBackground, + StackCustom, + TabBarBackground, // TextArea TextAreaCustom, // Text @@ -93,6 +102,5 @@ export { // TextInput TextInputCustom, // ViewWrapper - ViewWrapper + ViewWrapper, }; - diff --git a/screens/Collaboration/ProjectMainSelectedSection.tsx b/screens/Collaboration/ProjectMainSelectedSection.tsx new file mode 100644 index 0000000..3dba588 --- /dev/null +++ b/screens/Collaboration/ProjectMainSelectedSection.tsx @@ -0,0 +1,69 @@ +import { + AvatarCustom, + BaseBox, + CheckboxCustom, + CheckboxGroup, + Grid, + StackCustom, + TextCustom +} from "@/components"; +import { ICON_SIZE_SMALL } from "@/constants/constans-value"; +import { MaterialIcons } from "@expo/vector-icons"; +import { View } from "react-native"; + +export default function Collaboration_ProjectMainSelectedSection({ + selected, + setSelected, + setOpenDrawerParticipant, +}: { + selected: (string | number)[]; + setSelected: (value: (string | number)[]) => void; + setOpenDrawerParticipant: (value: boolean) => void; +}) { + return ( + + + + *{" "} + + Pilih user yang akan menjadi tim proyek anda + + + + + {Array.from({ length: 5 }).map((_, index) => ( + + + + + + + + + + + Username + + + + setOpenDrawerParticipant(true)} + /> + + + + ))} + + + + ); +}