feat: redesign input komentar diskusi divisi menggunakan komponen messaging app
This commit is contained in:
@@ -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<Props>();
|
||||
const [dataComment, setDataComment] = useState<PropsComment[]>([]);
|
||||
const [dataComment, setDataComment] = useState<CommentItem[]>([]);
|
||||
const [fileDiscussion, setFileDiscussion] = useState<PropsFile[]>([])
|
||||
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<any>([])
|
||||
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<string[]>([])
|
||||
|
||||
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() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<View style={Styles.mt10}>
|
||||
{loadingKomentar ? (
|
||||
arrSkeleton.map((_, i) => (
|
||||
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
|
||||
))
|
||||
) : (
|
||||
dataComment.map((item, i) => (
|
||||
<Pressable
|
||||
key={i}
|
||||
onPress={() => {
|
||||
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' }
|
||||
]}
|
||||
>
|
||||
<View style={Styles.flex1}>
|
||||
<View style={[Styles.rowSpaceBetween, Styles.itemsCenter, Styles.mb05]}>
|
||||
<View style={[Styles.rowItemsCenter, { gap: 8, flex: 1, marginRight: 8 }]}>
|
||||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
|
||||
<Text style={[Styles.textMediumSemiBold, { color: colors.text }]} numberOfLines={1}>
|
||||
{item.username}
|
||||
</Text>
|
||||
{item.isEdited && (
|
||||
<Text style={[Styles.discussionEditedText, { color: colors.dimmed }]}>diedit</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[Styles.discussionDateText, { color: colors.dimmed, flexShrink: 0 }]}>
|
||||
{item.createdAt}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[Styles.textDefault, { color: colors.text }]} numberOfLines={detailMore.includes(item.id) ? 0 : 3}>
|
||||
{item.comment}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
<DiscussionCommentList
|
||||
data={dataComment}
|
||||
loading={loadingKomentar}
|
||||
myId={entities.id}
|
||||
canInteract={data?.status != 2 && data?.isActive == true}
|
||||
onLongPress={handleMenuKomentar}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : undefined} keyboardVerticalOffset={headerHeight}>
|
||||
<View style={[Styles.contentItemCenter, Styles.w100, { backgroundColor: colors.background }, viewEdit && Styles.borderTop]}>
|
||||
{viewEdit ? (
|
||||
<>
|
||||
<View style={[Styles.w90, Styles.rowSpaceBetween, Styles.pv05]}>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<Feather name="edit-3" color={colors.text} size={22} style={Styles.mh05} />
|
||||
<Text style={Styles.textMediumSemiBold}>Edit Komentar</Text>
|
||||
</View>
|
||||
<Pressable onPress={() => handleViewEditKomentar()}>
|
||||
<MaterialIcons name="close" color={colors.text} size={22} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<InputForm
|
||||
bg={colors.card}
|
||||
type="default" round multiline
|
||||
placeholder="Kirim Komentar"
|
||||
onChange={(val: string) => setSelectKomentar({ ...selectKomentar, comment: val })}
|
||||
value={selectKomentar.comment}
|
||||
itemRight={
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
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]}
|
||||
>
|
||||
<MaterialIcons name="send" size={25}
|
||||
style={[
|
||||
selectKomentar.comment == "" || regexOnlySpacesOrEnter.test(selectKomentar.comment) || loadingSend || ((entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision)
|
||||
? { color: colors.dimmed } : { color: colors.tint },
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : canWrite ? (
|
||||
<InputForm
|
||||
type="default" round multiline
|
||||
placeholder="Kirim Komentar"
|
||||
onChange={setKomentar} value={komentar}
|
||||
itemRight={
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
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]}
|
||||
>
|
||||
<MaterialIcons name="send" size={25}
|
||||
style={[
|
||||
komentar == "" || regexOnlySpacesOrEnter.test(komentar) || loadingSend || ((entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision)
|
||||
? { color: colors.dimmed } : { color: colors.tint },
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<View style={[Styles.pv20, Styles.itemsCenter]}>
|
||||
<Text style={[Styles.textInformation, { color: colors.dimmed }]}>
|
||||
{data?.status == 2 ? "Diskusi telah ditutup" : data?.isActive == false ? "Diskusi telah diarsipkan" : "Hanya anggota divisi yang dapat memberikan komentar"}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<DiscussionCommentInput
|
||||
mode={
|
||||
viewEdit ? 'edit'
|
||||
: canWrite ? 'new'
|
||||
: 'locked'
|
||||
}
|
||||
lockedReason={
|
||||
data?.status == 2 ? "Diskusi telah ditutup"
|
||||
: data?.isActive == false ? "Diskusi telah diarsipkan"
|
||||
: "Hanya anggota divisi yang dapat memberikan komentar"
|
||||
}
|
||||
value={viewEdit ? selectKomentar.comment : komentar}
|
||||
onChange={viewEdit
|
||||
? (val) => 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}
|
||||
/>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user