diff --git a/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/index.tsx b/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/index.tsx index bf1301b..619fb98 100644 --- a/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/index.tsx @@ -2,15 +2,13 @@ import AppHeader from "@/components/AppHeader"; import BorderBottomItem2 from "@/components/borderBottomItem2"; import HeaderRightDiscussionDetail from "@/components/discussion/headerDiscussionDetail"; import DrawerBottom from "@/components/drawerBottom"; -import ImageUser from "@/components/imageNew"; -import { InputForm } from "@/components/inputForm"; +import DiscussionCommentInput from "@/components/discussion_general/discussionCommentInput"; +import DiscussionCommentList, { CommentFile, CommentItem } from "@/components/discussion_general/discussionCommentList"; import MenuItemRow from "@/components/menuItemRow"; import ModalConfirmation from "@/components/ModalConfirmation"; -import Skeleton from "@/components/skeleton"; import SkeletonContent from "@/components/skeletonContent"; import Text from "@/components/Text"; import { ConstEnv } from "@/constants/ConstEnv"; -import { regexOnlySpacesOrEnter } from "@/constants/OnlySpaceOrEnter"; import Styles from "@/constants/Styles"; import { apiDeleteDiscussionCommentar, @@ -18,6 +16,7 @@ import { apiGetDiscussionOne, apiGetDivisionOneFeature, apiSendDiscussionCommentar, + apiSendDiscussionCommentarWithFile, } from "@/lib/api"; import { getDB } from "@/lib/firebaseDatabase"; import { useAuthSession } from "@/providers/AuthProvider"; @@ -30,6 +29,7 @@ import { useEffect, useState } from "react"; import { KeyboardAvoidingView, Platform, Pressable, RefreshControl, ScrollView, View } from "react-native"; import Toast from "react-native-toast-message"; import { useSelector } from "react-redux"; +import ImageUser from "@/components/imageNew"; type Props = { id: string; @@ -44,17 +44,6 @@ type Props = { isActive: boolean; }; -type PropsComment = { - id: string; - comment: string; - createdAt: string; - username: string; - img: string; - idUser: string; - isEdited: boolean; - updatedAt: string; -}; - type PropsFile = { id: string; idStorage: string; @@ -66,10 +55,11 @@ export default function DiscussionDetail() { const { colors } = useTheme(); const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>(); const [data, setData] = useState(); - const [dataComment, setDataComment] = useState([]); + const [dataComment, setDataComment] = useState([]); const [fileDiscussion, setFileDiscussion] = useState([]) const { token, decryptToken } = useAuthSession(); const [komentar, setKomentar] = useState(""); + const [commentFiles, setCommentFiles] = useState<{ uri: string; name: string }[]>([]) const [loadingSend, setLoadingSend] = useState(false); const update = useSelector((state: any) => state.discussionUpdate); const entityUser = useSelector((state: any) => state.user); @@ -78,16 +68,15 @@ export default function DiscussionDetail() { const [isCreator, setIsCreator] = useState(false); const [loading, setLoading] = useState(true) const [loadingKomentar, setLoadingKomentar] = useState(true) - const arrSkeleton = Array.from({ length: 3 }) const reference = ref(getDB(), `/discussion-division/${detail}`); const [refreshing, setRefreshing] = useState(false) const headerHeight = useHeaderHeight(); - const [detailMore, setDetailMore] = useState([]) const entities = useSelector((state: any) => state.entities) const [isVisible, setVisible] = useState(false) - const [selectKomentar, setSelectKomentar] = useState({ id: '', comment: '' }) + const [selectKomentar, setSelectKomentar] = useState<{ id: string; comment: string; files: CommentFile[] }>({ id: '', comment: '', files: [] }) const [viewEdit, setViewEdit] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false) + const [removedFileIds, setRemovedFileIds] = useState([]) useEffect(() => { const onValueChange = reference.on('value', snapshot => { @@ -152,8 +141,22 @@ export default function DiscussionDetail() { try { setLoadingSend(true); const hasil = await decryptToken(String(token?.current)); - const response = await apiSendDiscussionCommentar({ id: detail, data: { comment: komentar, user: hasil } }); - if (response.success) { setKomentar(""); updateTrigger() } + let response + if (commentFiles.length > 0) { + const fd = new FormData() + fd.append('data', JSON.stringify({ comment: komentar, user: hasil })) + commentFiles.forEach((f, i) => { + fd.append(`file${i}`, { uri: f.uri, name: f.name, type: 'application/octet-stream' } as any) + }) + response = await apiSendDiscussionCommentarWithFile(detail, fd) + } else { + response = await apiSendDiscussionCommentar({ id: detail, data: { comment: komentar, user: hasil } }); + } + if (response.success) { + setKomentar(""); + setCommentFiles([]) + updateTrigger() + } } catch (error: any) { console.error(error); Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal menambahkan komentar" }) @@ -166,7 +169,10 @@ export default function DiscussionDetail() { try { setLoadingSend(true); const hasil = await decryptToken(String(token?.current)); - const response = await apiEditDiscussionCommentar({ id: selectKomentar.id, data: { comment: selectKomentar.comment, user: hasil } }); + const response = await apiEditDiscussionCommentar({ + id: selectKomentar.id, + data: { comment: selectKomentar.comment, user: hasil, filesToRemove: removedFileIds } + }); if (response.success) { updateTrigger() } else { Toast.show({ type: 'small', text1: response.message }) } } catch (error: any) { console.error(error); @@ -192,14 +198,20 @@ export default function DiscussionDetail() { } } - function handleMenuKomentar(id: string, comment: string) { - setSelectKomentar({ id, comment }) + function handleMenuKomentar(id: string, comment: string, files: CommentFile[]) { + setSelectKomentar({ id, comment, files }) + setRemovedFileIds([]) setVisible(true) } function handleViewEditKomentar() { setVisible(false) setViewEdit(!viewEdit) + if (viewEdit) setRemovedFileIds([]) + } + + function handleRemoveExistingFile(fileId: string) { + setRemovedFileIds(prev => prev.includes(fileId) ? prev.filter(id => id !== fileId) : [...prev, fileId]) } const handleRefresh = async () => { @@ -212,6 +224,9 @@ export default function DiscussionDetail() { const canWrite = data?.status != 2 && data?.isActive && ((entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision) const isOpen = data?.status === 1 + const canSend = (entityUser.role == "admin" || entityUser.role == "supadmin" || entityUser.role == "developer" || entityUser.role == "cosupadmin") || isMemberDivision + + const existingFilesForEdit = selectKomentar.files.filter(f => !removedFileIds.includes(f.id)) return ( <> @@ -273,123 +288,43 @@ export default function DiscussionDetail() { /> )} - - {loadingKomentar ? ( - arrSkeleton.map((_, i) => ( - - )) - ) : ( - dataComment.map((item, i) => ( - { - setDetailMore((prev: any) => - prev.includes(item.id) ? prev.filter((id: string) => id !== item.id) : [...prev, item.id] - ) - }} - onLongPress={() => { - item.idUser == entities.id && data?.status != 2 && data?.isActive && handleMenuKomentar(item.id, item.comment) - }} - style={({ pressed }) => [ - Styles.discussionCommentCard, - { backgroundColor: pressed ? colors.icon + '10' : colors.card, borderColor: colors.icon + '20' } - ]} - > - - - - - - {item.username} - - {item.isEdited && ( - diedit - )} - - - {item.createdAt} - - - - {item.comment} - - - - )) - )} - + - - {viewEdit ? ( - <> - - - - Edit Komentar - - handleViewEditKomentar()}> - - - - setSelectKomentar({ ...selectKomentar, comment: val })} - value={selectKomentar.comment} - itemRight={ - { - selectKomentar.comment != "" && !regexOnlySpacesOrEnter.test(selectKomentar.comment) && !loadingSend && data?.status != 2 && data?.isActive - && (((entityUser.role == "user" || entityUser.role == "coadmin") && isMemberDivision) || entityUser.role == "admin" || entityUser.role == "supadmin" || entityUser.role == "developer" || entityUser.role == "cosupadmin") - && handleEditKomentar(); - }} - style={[Platform.OS == 'android' && Styles.mb12]} - > - - - } - /> - - ) : canWrite ? ( - { - komentar != "" && !regexOnlySpacesOrEnter.test(komentar) && !loadingSend && data?.status != 2 && data?.isActive - && (((entityUser.role == "user" || entityUser.role == "coadmin") && isMemberDivision) || entityUser.role == "admin" || entityUser.role == "supadmin" || entityUser.role == "developer" || entityUser.role == "cosupadmin") - && handleKomentar(); - }} - style={[Platform.OS == 'android' && Styles.mb12]} - > - - - } - /> - ) : ( - - - {data?.status == 2 ? "Diskusi telah ditutup" : data?.isActive == false ? "Diskusi telah diarsipkan" : "Hanya anggota divisi yang dapat memberikan komentar"} - - - )} - + setSelectKomentar(prev => ({ ...prev, comment: val })) + : setKomentar + } + loading={loadingSend} + onSend={viewEdit ? handleEditKomentar : handleKomentar} + onCancelEdit={handleViewEditKomentar} + files={viewEdit ? [] : commentFiles} + onAddFile={viewEdit ? undefined : (f) => setCommentFiles(prev => [...prev, ...f])} + onRemoveFile={viewEdit ? undefined : (idx) => setCommentFiles(prev => prev.filter((_, i) => i !== idx))} + existingFiles={viewEdit ? existingFilesForEdit : []} + onRemoveExistingFile={viewEdit ? handleRemoveExistingFile : undefined} + canSend={canSend} + /> diff --git a/lib/api/discussion.api.ts b/lib/api/discussion.api.ts index eeb42a7..dca7d0e 100644 --- a/lib/api/discussion.api.ts +++ b/lib/api/discussion.api.ts @@ -105,7 +105,14 @@ export const apiSendDiscussionCommentar = async ({ data, id }: { data: { user: s return response.data; }; -export const apiEditDiscussionCommentar = async ({ data, id }: { data: { user: string, comment: string }, id: string }) => { +export const apiSendDiscussionCommentarWithFile = async (id: string, data: FormData) => { + const response = await api.post(`/mobile/discussion/${id}/comment`, data, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + return response.data; +}; + +export const apiEditDiscussionCommentar = async ({ data, id }: { data: { user: string, comment: string, filesToRemove?: string[] }, id: string }) => { const response = await api.put(`/mobile/discussion/${id}/comment`, data) return response.data; };