diff --git a/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/edit.tsx b/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/edit.tsx index 360a9d3..5bddeb3 100644 --- a/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/edit.tsx +++ b/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/edit.tsx @@ -1,7 +1,5 @@ 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"; import { InputForm } from "@/components/inputForm"; import LoadingCenter from "@/components/loadingCenter"; @@ -16,10 +14,30 @@ 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 DiscussionDivisionEdit() { const { colors } = useTheme(); const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>(); @@ -33,30 +51,49 @@ export default function DiscussionDivisionEdit() { const [indexDelFile, setIndexDelFile] = useState<{ id: string | number; cat: "newFile" | "oldFile" }>({ id: "", cat: "newFile" }) const [dataFile, setDataFile] = useState<{ id: string; idStorage: string; name: string; extension: string; delete?: boolean }[]>([]) + const visibleOldFiles = dataFile.filter(v => !v.delete) + const totalFiles = fileForm.length + visibleOldFiles.length async function handleLoad() { try { const hasil = await decryptToken(String(token?.current)); - const response = await apiGetDiscussionOne({ - id: detail, - user: hasil, - cat: "data", - }); - const response2 = await apiGetDiscussionOne({ - id: detail, - user: hasil, - cat: "file", - }); - setDataFile(response2.data); + const response = await apiGetDiscussionOne({ id: detail, user: hasil, cat: "data" }); + const response2 = await apiGetDiscussionOne({ id: detail, user: hasil, cat: "file" }); setData(response.data.desc); + setDataFile(response2.data); } catch (error) { console.error(error); } } - useEffect(() => { - handleLoad(); - }, []); + useEffect(() => { handleLoad() }, []); + + const pickDocumentAsync = async () => { + const result = await DocumentPicker.getDocumentAsync({ type: ["*/*"], multiple: true }); + if (!result.canceled) { + let skipped = 0 + for (const asset of result.assets) { + if (!asset.uri) continue + const isDup = fileForm.some(f => f.name === asset.name) || + visibleOldFiles.some(f => `${f.name}.${f.extension}` === asset.name) + if (isDup) { + skipped++ + } else { + setFileForm(prev => [...prev, asset]) + } + } + if (skipped > 0) Toast.show({ type: 'small', text1: 'Beberapa file sudah ditambahkan' }) + } + }; + + function deleteFile(index: number | string, cat: "newFile" | "oldFile" | null) { + if (cat === "newFile") { + setFileForm(fileForm.filter((_, i) => i !== index)) + } else { + setDataFile(prev => prev.map(item => item.id === index ? { ...item, delete: true } : item)) + } + setModalFile(false) + } async function handleUpdate() { try { @@ -64,94 +101,29 @@ export default function DiscussionDivisionEdit() { const hasil = await decryptToken(String(token?.current)); const fd = new FormData() for (let i = 0; i < fileForm.length; i++) { - fd.append(`file${i}`, { - uri: fileForm[i].uri, - type: 'application/octet-stream', - name: fileForm[i].name, - } as any); + fd.append(`file${i}`, { uri: fileForm[i].uri, type: 'application/octet-stream', name: fileForm[i].name } as any); } - - fd.append("data", JSON.stringify( - { - user: hasil, desc: data, oldFile: dataFile - } - )) + fd.append("data", JSON.stringify({ user: hasil, desc: data, oldFile: dataFile })) const response = await apiEditDiscussion(fd, detail); - - // const response = await apiEditDiscussion({ - // data: { user: hasil, desc: data }, - // id: detail, - // }); if (response.success) { - Toast.show({ type: 'small', text1: 'Berhasil mengubah data', }) + Toast.show({ type: 'small', text1: 'Berhasil mengubah data' }) dispatch(setUpdateDiscussion({ ...update, data: !update.data })); router.back(); } else { - Toast.show({ type: 'small', text1: response.message, }) + Toast.show({ type: 'small', text1: response.message }) } } catch (error: any) { console.error(error); - const message = error?.response?.data?.message || "Gagal mengubah data" - - Toast.show({ type: 'small', text1: message }) + Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal mengubah data" }) } finally { setLoading(false) } } - const pickDocumentAsync = async () => { - let result = await DocumentPicker.getDocumentAsync({ - type: ["*/*"], - multiple: true - }); - if (!result.canceled) { - for (let i = 0; i < result.assets?.length; i++) { - if (result.assets[i].uri) { - setFileForm((prev) => [...prev, result.assets[i]]) - } - } - } - }; - - - - function deleteFile(index: number | string, cat: "newFile" | "oldFile" | null) { - if (cat == "newFile") { - setFileForm([...fileForm.filter((val, i) => i !== index)]) - } else { - setDataFile(prev => - prev.map(item => - item.id === index - ? { ...item, delete: true } - : item - ) - ); - } - setModalFile(false) - } - return ( - + ( - // { - // router.back(); - // }} - // /> - // ), - headerTitle: "Edit Diskusi", - headerTitleAlign: "center", - // headerRight: () => ( - // { - // handleUpdate(); - // }} - // /> - // ), header: () => ( { - handleUpdate(); - }} + onPress={() => handleUpdate()} /> } /> @@ -171,8 +141,8 @@ export default function DiscussionDivisionEdit() { }} /> {loading && } - - + + - - { - (fileForm.length > 0 || dataFile.filter((val) => !val.delete).length > 0) - && - <> - - File - {fileForm.length + dataFile.filter((val) => !val.delete).length} file + {/* File */} + + 0 ? 12 : 0 }]} + > + + - - { - dataFile.filter((val) => !val.delete).map((item, index) => ( - !val.delete).length - 1 == index && fileForm.length == 0 ? "none" : "bottom"} - icon={} - title={item.name + '.' + item.extension} - titleWeight="normal" + + File + {totalFiles === 0 && ( + Opsional — ketuk untuk upload + )} + + {totalFiles > 0 && ( + + {totalFiles} file + + )} + + + {totalFiles > 0 && ( + + {visibleOldFiles.map((item, index) => { + const ext = item.extension.toLowerCase() + const iconName = getFileIcon(ext) + const iconColor = getFileColor(ext) + return ( + { setIndexDelFile({ id: item.id, cat: "oldFile" }); setModalFile(true) }} - /> - )) - } - { - fileForm.map((item, index) => ( - } - title={item.name} - titleWeight="normal" + style={[Styles.fileCard, { backgroundColor: 'transparent', borderColor: colors.icon + '18' }]} + > + + + + + {item.name} + {ext.toUpperCase()} + + + ) + })} + {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({ id: index, cat: "newFile" }); setModalFile(true) }} - /> - )) - } + style={[Styles.fileCard, { backgroundColor: 'transparent', borderColor: colors.icon + '18' }]} + > + + + + + {baseName} + {ext.toUpperCase()} + + + ) + })} - - } + )} + - } title="Hapus" - onPress={() => { deleteFile(indexDelFile.id, indexDelFile.cat) }} + onPress={() => deleteFile(indexDelFile.id, indexDelFile.cat)} /> - ); } diff --git a/app/(application)/division/[id]/(fitur-division)/discussion/create.tsx b/app/(application)/division/[id]/(fitur-division)/discussion/create.tsx index 61d1b0a..4d5d487 100644 --- a/app/(application)/division/[id]/(fitur-division)/discussion/create.tsx +++ b/app/(application)/division/[id]/(fitur-division)/discussion/create.tsx @@ -1,7 +1,5 @@ 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" import { InputForm } from "@/components/inputForm" import LoadingCenter from "@/components/loadingCenter" @@ -16,10 +14,29 @@ import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons" import * as DocumentPicker from "expo-document-picker" import { router, Stack, useLocalSearchParams } from "expo-router" import { 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 CreateDiscussionDivision() { const { colors } = useTheme(); @@ -34,76 +51,55 @@ export default function CreateDiscussionDivision() { const [indexDelFile, setIndexDelFile] = useState(0) const pickDocumentAsync = async () => { - let result = await DocumentPicker.getDocumentAsync({ - type: ["*/*"], - multiple: true - }); + const result = await DocumentPicker.getDocumentAsync({ type: ["*/*"], multiple: true }); if (!result.canceled) { - for (let i = 0; i < result.assets?.length; i++) { - if (result.assets[i].uri) { - setFileForm((prev) => [...prev, result.assets[i]]) + let skipped = 0 + for (const asset of result.assets) { + if (!asset.uri) continue + if (fileForm.some(f => f.name === asset.name)) { + skipped++ + } else { + setFileForm(prev => [...prev, asset]) } } + if (skipped > 0) Toast.show({ type: 'small', text1: 'Beberapa file sudah ditambahkan' }) } }; function deleteFile(index: number) { - setFileForm([...fileForm.filter((val, i) => i !== index)]) + setFileForm(fileForm.filter((_, i) => i !== index)) setModalFile(false) } - async function handleCreate() { try { setLoading(true) const hasil = await decryptToken(String(token?.current)) const fd = new FormData() - for (let i = 0; i < fileForm.length; i++) { - fd.append(`file${i}`, { - uri: fileForm[i].uri, - type: 'application/octet-stream', - name: fileForm[i].name, - } as any); + fd.append(`file${i}`, { uri: fileForm[i].uri, type: 'application/octet-stream', name: fileForm[i].name } as any); } - - fd.append("data", JSON.stringify( - { user: hasil, desc, idDivision: id } - )) - + fd.append("data", JSON.stringify({ user: hasil, desc, idDivision: id })) const response = await apiCreateDiscussion(fd) - - // const response = await apiCreateDiscussion({ data: { user: hasil, desc, idDivision: id } }) if (response.success) { - Toast.show({ type: 'small', text1: 'Berhasil menambahkan data', }) + Toast.show({ type: 'small', text1: 'Berhasil menambahkan data' }) dispatch(setUpdateDiscussion({ ...update, data: !update.data })); router.back() } else { - Toast.show({ type: 'small', text1: response.message, }) + Toast.show({ type: 'small', text1: response.message }) } } catch (error: any) { console.error(error); - const message = error?.response?.data?.message || "Gagal menambahkan data" - - Toast.show({ type: 'small', text1: message }) + Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal menambahkan data" }) } finally { setLoading(false) } } return ( - + { router.back() }} />, - headerTitle: 'Tambah Diskusi', - headerTitleAlign: 'center', - // headerRight: () => { - // handleCreate() - // }} /> header: () => ( { - handleCreate() - }} /> + onPress={() => handleCreate()} + /> } /> ) }} /> {loading && } - + - - { - fileForm.length > 0 - && - <> - - File - {fileForm.length} file - - - { - fileForm.map((item, index) => ( - } - title={item.name} - titleWeight="normal" - onPress={() => { setIndexDelFile(index); setModalFile(true) }} - /> - )) - } - - - } + {/* File */} + + 0 ? 12 : 0 }]} + > + + + + + File + {fileForm.length === 0 && ( + Opsional — ketuk untuk upload + )} + + {fileForm.length > 0 && ( + + {fileForm.length} file + + )} + + + {fileForm.length > 0 && ( + + {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); setModalFile(true) }} + style={[Styles.fileCard, { backgroundColor: 'transparent', borderColor: colors.icon + '18' }]} + > + + + + + {baseName} + {ext.toUpperCase()} + + + ) + })} + + )} + @@ -167,10 +186,10 @@ export default function CreateDiscussionDivision() { } title="Hapus" - onPress={() => { deleteFile(indexDelFile) }} + onPress={() => deleteFile(indexDelFile)} /> ) -} \ No newline at end of file +}