From 11bb1ddc98bb3274f90ad051b0633c7f6b314225 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Wed, 6 May 2026 16:50:54 +0800 Subject: [PATCH] feat: terapkan design baru pada halaman create project dan create task divisi - Ganti file list (BorderBottomItem) dengan fileGrid/fileCard bergaya baru dengan icon berwarna sesuai tipe file - Ganti member section dengan card individual per anggota (avatar + nama + badge jabatan) - Header anggota: label kiri + jumlah orang di kanan - Simpan field position saat memilih anggota di modalSelect - Hapus wrapper wrapPaper di SectionListAddTask Co-Authored-By: Claude Sonnet 4.6 --- .../[id]/(fitur-division)/task/create.tsx | 129 ++++++++++++------ app/(application)/project/create.tsx | 128 +++++++++++------ components/modalSelect.tsx | 8 +- components/project/sectionListAddTask.tsx | 35 +++-- 4 files changed, 190 insertions(+), 110 deletions(-) diff --git a/app/(application)/division/[id]/(fitur-division)/task/create.tsx b/app/(application)/division/[id]/(fitur-division)/task/create.tsx index f6d2b34..6252ff4 100644 --- a/app/(application)/division/[id]/(fitur-division)/task/create.tsx +++ b/app/(application)/division/[id]/(fitur-division)/task/create.tsx @@ -1,5 +1,4 @@ import AppHeader from "@/components/AppHeader"; -import BorderBottomItem from "@/components/borderBottomItem"; import ButtonSaveHeader from "@/components/buttonSaveHeader"; import ButtonSelect from "@/components/buttonSelect"; import DrawerBottom from "@/components/drawerBottom"; @@ -21,11 +20,31 @@ import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons"; import * as DocumentPicker from "expo-document-picker"; import { router, Stack, useLocalSearchParams } from "expo-router"; import { useEffect, useState } from "react"; -import { SafeAreaView, ScrollView, View } from "react-native"; +import { Pressable, SafeAreaView, ScrollView, View } from "react-native"; import Toast from "react-native-toast-message"; import { useDispatch, useSelector } from "react-redux"; +function getFileIcon(ext: string): keyof typeof MaterialCommunityIcons.glyphMap { + if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif'].includes(ext)) return 'image-outline' + if (ext === 'pdf') return 'file-pdf-box' + if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return 'video-outline' + if (['doc', 'docx'].includes(ext)) return 'file-word-outline' + if (['xls', 'xlsx'].includes(ext)) return 'file-excel-outline' + if (['zip', 'rar', '7z'].includes(ext)) return 'zip-box-outline' + return 'file-outline' +} + +function getFileColor(ext: string): string { + if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif'].includes(ext)) return '#339AF0' + if (ext === 'pdf') return '#F03E3E' + if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return '#AE3EC9' + if (['doc', 'docx'].includes(ext)) return '#1C7ED6' + if (['xls', 'xlsx'].includes(ext)) return '#2F9E44' + if (['zip', 'rar', '7z'].includes(ext)) return '#E8590C' + return '#868E96' +} + export default function CreateTaskDivision() { const { colors } = useTheme(); const { id } = useLocalSearchParams(); @@ -172,52 +191,74 @@ export default function CreateTaskDivision() { { router.push(`/division/${id}/task/create/member`); }} /> - { - fileForm.length > 0 && ( - - File - - { - fileForm.map((item, index) => ( - } - title={item.name} - titleWeight="normal" - onPress={() => { setIndexDelFile(index); setModal(true) }} - /> - )) - } - + {fileForm.length > 0 && ( + + File + + {fileForm.map((item, index) => { + const ext = item.name.split('.').pop()?.toLowerCase() ?? '' + const baseName = item.name.includes('.') ? item.name.split('.').slice(0, -1).join('.') : item.name + const iconName = getFileIcon(ext) + const iconColor = getFileColor(ext) + return ( + { setIndexDelFile(index); setModal(true) }} + style={[Styles.fileCard, { backgroundColor: colors.card, borderColor: colors.icon + '18' }]} + > + + + + + {baseName} + + {ext.toUpperCase()} + + + + ) + })} - ) - } + + )} {entitiesMember.length > 0 && ( - + - Anggota - Total {entitiesMember.length} Anggota + Anggota + {entitiesMember.length} orang - - - {entitiesMember.map( - (item: { img: any; name: any }, index: any) => { - return ( - - } - title={item.name} - /> - ); - } - )} + + {entitiesMember.map((item: { img: any; name: any; position?: string }, index: any) => ( + + + {item.name} + {item.position && ( + + + {item.position} + + + )} + + ))} )} diff --git a/app/(application)/project/create.tsx b/app/(application)/project/create.tsx index 34de7be..e1623b9 100644 --- a/app/(application)/project/create.tsx +++ b/app/(application)/project/create.tsx @@ -1,5 +1,4 @@ import AppHeader from "@/components/AppHeader"; -import BorderBottomItem from "@/components/borderBottomItem"; import ButtonSaveHeader from "@/components/buttonSaveHeader"; import ButtonSelect from "@/components/buttonSelect"; import DrawerBottom from "@/components/drawerBottom"; @@ -25,6 +24,7 @@ import * as DocumentPicker from "expo-document-picker"; import { router, Stack } from "expo-router"; import { useEffect, useState } from "react"; import { + Pressable, SafeAreaView, ScrollView, View @@ -32,6 +32,26 @@ import { import Toast from "react-native-toast-message"; import { useDispatch, useSelector } from "react-redux"; +function getFileIcon(ext: string): keyof typeof MaterialCommunityIcons.glyphMap { + if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif'].includes(ext)) return 'image-outline' + if (ext === 'pdf') return 'file-pdf-box' + if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return 'video-outline' + if (['doc', 'docx'].includes(ext)) return 'file-word-outline' + if (['xls', 'xlsx'].includes(ext)) return 'file-excel-outline' + if (['zip', 'rar', '7z'].includes(ext)) return 'zip-box-outline' + return 'file-outline' +} + +function getFileColor(ext: string): string { + if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif'].includes(ext)) return '#339AF0' + if (ext === 'pdf') return '#F03E3E' + if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return '#AE3EC9' + if (['doc', 'docx'].includes(ext)) return '#1C7ED6' + if (['xls', 'xlsx'].includes(ext)) return '#2F9E44' + if (['zip', 'rar', '7z'].includes(ext)) return '#E8590C' + return '#868E96' +} + export default function CreateProject() { const { colors } = useTheme(); const [loading, setLoading] = useState(false) @@ -300,52 +320,74 @@ export default function CreateProject() { errorText="Anggota tidak boleh kosong" /> - { - fileForm.length > 0 && ( - - File - - { - fileForm.map((item, index) => ( - } - title={item.name} - titleWeight="normal" - onPress={() => { setIndexDelFile(index); setModal(true) }} - /> - )) - } - + {fileForm.length > 0 && ( + + File + + {fileForm.map((item, index) => { + const ext = item.name.split('.').pop()?.toLowerCase() ?? '' + const baseName = item.name.includes('.') ? item.name.split('.').slice(0, -1).join('.') : item.name + const iconName = getFileIcon(ext) + const iconColor = getFileColor(ext) + return ( + { setIndexDelFile(index); setModal(true) }} + style={[Styles.fileCard, { backgroundColor: colors.card, borderColor: colors.icon + '18' }]} + > + + + + + {baseName} + + {ext.toUpperCase()} + + + + ) + })} - ) - } + + )} {entitiesMember.length > 0 && ( - + - Anggota - Total {entitiesMember.length} Anggota + Anggota + {entitiesMember.length} orang - - - {entitiesMember.map( - (item: { img: any; name: any }, index: any) => { - return ( - - } - title={item.name} - /> - ); - } - )} + + {entitiesMember.map((item: { img: any; name: any; position?: string }, index: any) => ( + + + {item.name} + {item.position && ( + + + {item.position} + + + )} + + ))} )} diff --git a/components/modalSelect.tsx b/components/modalSelect.tsx index 2b68035..e8418a0 100644 --- a/components/modalSelect.tsx +++ b/components/modalSelect.tsx @@ -108,12 +108,12 @@ export default function ModalSelect({ open, close, title, category, idParent, on setChooseValue({ ...chooseValue, val: valChoose }) }, [dispatch, open, search]); - function onChoose(val: string, label: string, img?: string) { + function onChoose(val: string, label: string, img?: string, position?: string) { if (category == "member") { if (selectMember.some((i: any) => i.idUser == val)) { setSelectMember(selectMember.filter((i: any) => i.idUser != val)) } else { - setSelectMember([...selectMember, { idUser: val, name: label, img }]) + setSelectMember([...selectMember, { idUser: val, name: label, img, position }]) } } else { setChooseValue({ val, label }) @@ -144,7 +144,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on key={index} label={item.name} src={`${ConstEnv.url_storage}/files/${item.img}`} - onClick={() => onChoose(item.idUser, item.name, item.img)} + onClick={() => onChoose(item.idUser, item.name, item.img, item.position)} /> )) } @@ -162,7 +162,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on category != 'status-task' ? data.length > 0 ? data.map((item: any, index: any) => ( - { onChoose(item.id, item.name, item.img) }}> + { onChoose(item.id, item.name, item.img, item.position) }}> { category == 'member' ? diff --git a/components/project/sectionListAddTask.tsx b/components/project/sectionListAddTask.tsx index 197ef87..a403412 100644 --- a/components/project/sectionListAddTask.tsx +++ b/components/project/sectionListAddTask.tsx @@ -32,25 +32,22 @@ export default function SectionListAddTask() { Tanggal & Tugas - - { - taskCreate.map((item: { status: number; title: string; dateStart: string; dateEnd: string; }, index: Key | null | undefined) => { - return ( - { - setSelect(index) - setModal(true) - }} - /> - ); - }) - } - - + { + taskCreate.map((item: { status: number; title: string; dateStart: string; dateEnd: string; }, index: Key | null | undefined) => { + return ( + { + setSelect(index) + setModal(true) + }} + /> + ); + }) + }