415 lines
19 KiB
TypeScript
415 lines
19 KiB
TypeScript
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 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,
|
|
apiEditDiscussionCommentar,
|
|
apiGetDiscussionOne,
|
|
apiGetDivisionOneFeature,
|
|
apiSendDiscussionCommentar,
|
|
} from "@/lib/api";
|
|
import { getDB } from "@/lib/firebaseDatabase";
|
|
import { useAuthSession } from "@/providers/AuthProvider";
|
|
import { useTheme } from "@/providers/ThemeProvider";
|
|
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
|
import { ref } from "@react-native-firebase/database";
|
|
import { useHeaderHeight } from '@react-navigation/elements';
|
|
import { router, Stack, useLocalSearchParams } from "expo-router";
|
|
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";
|
|
|
|
type Props = {
|
|
id: string;
|
|
title: string;
|
|
desc: string;
|
|
status: number;
|
|
createdAt: string;
|
|
createdBy: string;
|
|
username: string;
|
|
user_img: string;
|
|
isCreator: boolean;
|
|
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;
|
|
name: string;
|
|
extension: string
|
|
}
|
|
|
|
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 [fileDiscussion, setFileDiscussion] = useState<PropsFile[]>([])
|
|
const { token, decryptToken } = useAuthSession();
|
|
const [komentar, setKomentar] = useState("");
|
|
const [loadingSend, setLoadingSend] = useState(false);
|
|
const update = useSelector((state: any) => state.discussionUpdate);
|
|
const entityUser = useSelector((state: any) => state.user);
|
|
const [isMemberDivision, setIsMemberDivision] = useState(false);
|
|
const [isAdminDivision, setIsAdminDivision] = useState(false);
|
|
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 [viewEdit, setViewEdit] = useState(false)
|
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const onValueChange = reference.on('value', snapshot => {
|
|
if (snapshot.val() == null) { reference.set({ trigger: true }) }
|
|
handleLoadComment(false)
|
|
});
|
|
return () => reference.off('value', onValueChange);
|
|
}, []);
|
|
|
|
function updateTrigger() {
|
|
reference.once('value', snapshot => {
|
|
const data = snapshot.val();
|
|
reference.update({ trigger: !data.trigger });
|
|
});
|
|
}
|
|
|
|
async function handleLoad(loading: boolean) {
|
|
try {
|
|
setLoading(loading)
|
|
const hasil = await decryptToken(String(token?.current));
|
|
const response = await apiGetDiscussionOne({ id: detail, user: hasil, cat: "data" });
|
|
const responseFile = await apiGetDiscussionOne({ id: detail, user: hasil, cat: "file" });
|
|
setData(response.data);
|
|
setFileDiscussion(responseFile.data)
|
|
setIsCreator(response.data.createdBy == hasil);
|
|
} catch (error) {
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
async function handleLoadComment(loading: boolean) {
|
|
try {
|
|
setLoadingKomentar(loading)
|
|
const hasil = await decryptToken(String(token?.current));
|
|
const response = await apiGetDiscussionOne({ id: detail, user: hasil, cat: "comment" });
|
|
setDataComment(response.data);
|
|
} catch (error) {
|
|
console.error(error);
|
|
} finally {
|
|
setLoadingKomentar(false)
|
|
}
|
|
}
|
|
|
|
async function handleCheckMember() {
|
|
try {
|
|
const hasil = await decryptToken(String(token?.current));
|
|
const response = await apiGetDivisionOneFeature({ id, user: hasil, cat: "check-member" });
|
|
const response2 = await apiGetDivisionOneFeature({ id, user: hasil, cat: "check-admin" });
|
|
setIsMemberDivision(response.data);
|
|
setIsAdminDivision(response2.data);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
useEffect(() => { handleLoad(false); }, [update.data]);
|
|
useEffect(() => { handleLoad(true); handleLoadComment(true); handleCheckMember(); }, []);
|
|
|
|
async function handleKomentar() {
|
|
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() }
|
|
} catch (error: any) {
|
|
console.error(error);
|
|
Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal menambahkan komentar" })
|
|
} finally {
|
|
setLoadingSend(false);
|
|
}
|
|
}
|
|
|
|
async function handleEditKomentar() {
|
|
try {
|
|
setLoadingSend(true);
|
|
const hasil = await decryptToken(String(token?.current));
|
|
const response = await apiEditDiscussionCommentar({ id: selectKomentar.id, data: { comment: selectKomentar.comment, user: hasil } });
|
|
if (response.success) { updateTrigger() } else { Toast.show({ type: 'small', text1: response.message }) }
|
|
} catch (error: any) {
|
|
console.error(error);
|
|
Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal mengedit komentar" })
|
|
} finally {
|
|
setLoadingSend(false);
|
|
handleViewEditKomentar()
|
|
}
|
|
}
|
|
|
|
async function handleDeleteKomentar() {
|
|
try {
|
|
setLoadingSend(true);
|
|
const hasil = await decryptToken(String(token?.current));
|
|
const response = await apiDeleteDiscussionCommentar({ id: selectKomentar.id, data: { user: hasil } });
|
|
if (response.success) { updateTrigger() } else { Toast.show({ type: 'small', text1: response.message }) }
|
|
} catch (error: any) {
|
|
console.error(error);
|
|
Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal menghapus komentar" })
|
|
} finally {
|
|
setLoadingSend(false)
|
|
setVisible(false)
|
|
}
|
|
}
|
|
|
|
function handleMenuKomentar(id: string, comment: string) {
|
|
setSelectKomentar({ id, comment })
|
|
setVisible(true)
|
|
}
|
|
|
|
function handleViewEditKomentar() {
|
|
setVisible(false)
|
|
setViewEdit(!viewEdit)
|
|
}
|
|
|
|
const handleRefresh = async () => {
|
|
setRefreshing(true)
|
|
handleLoad(false)
|
|
handleLoadComment(false)
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
setRefreshing(false)
|
|
};
|
|
|
|
const canWrite = data?.status != 2 && data?.isActive && ((entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision)
|
|
const isOpen = data?.status === 1
|
|
|
|
return (
|
|
<>
|
|
<Stack.Screen
|
|
options={{
|
|
headerTitle: "Diskusi",
|
|
headerTitleAlign: "center",
|
|
header: () => (
|
|
<AppHeader
|
|
title="Diskusi"
|
|
showBack={true}
|
|
onPressLeft={() => router.back()}
|
|
right={
|
|
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision || isCreator) ?
|
|
<HeaderRightDiscussionDetail id={detail} status={data?.status} isActive={data?.isActive} /> : undefined
|
|
}
|
|
/>
|
|
)
|
|
}}
|
|
/>
|
|
<View style={[Styles.flex1, { backgroundColor: colors.background }]}>
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={colors.icon} />}
|
|
>
|
|
<View style={[Styles.p15]}>
|
|
{loading ? (
|
|
<SkeletonContent />
|
|
) : (
|
|
<BorderBottomItem2
|
|
dataFile={fileDiscussion}
|
|
descEllipsize={false}
|
|
borderType="all"
|
|
bgColor="white"
|
|
icon={
|
|
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.user_img}`} size="sm" />
|
|
}
|
|
title={data?.username}
|
|
titleShowAll={true}
|
|
subtitle={
|
|
<View style={[Styles.discussionStatusPill, {
|
|
borderColor: !data?.isActive ? '#F59E0B' : isOpen ? '#10B981' : colors.dimmed + '80',
|
|
}]}>
|
|
<Text style={[Styles.discussionStatusText, {
|
|
color: !data?.isActive ? '#F59E0B' : isOpen ? '#10B981' : colors.dimmed,
|
|
}]}>
|
|
{!data?.isActive ? 'Arsip' : isOpen ? 'Buka' : 'Tutup'}
|
|
</Text>
|
|
</View>
|
|
}
|
|
desc={data?.desc}
|
|
leftBottomInfo={
|
|
<View style={Styles.rowItemsCenter}>
|
|
<Feather name="message-square" size={14} color={colors.dimmed} style={Styles.mr05} />
|
|
<Text style={[Styles.textInformation, { color: colors.dimmed }]}>{dataComment.length} Komentar</Text>
|
|
</View>
|
|
}
|
|
rightBottomInfo={<Text style={[Styles.textInformation, { color: colors.dimmed }]}>{data?.createdAt}</Text>}
|
|
/>
|
|
)}
|
|
|
|
<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>
|
|
</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>
|
|
</KeyboardAvoidingView>
|
|
</View>
|
|
|
|
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Komentar">
|
|
<View style={Styles.rowItemsCenter}>
|
|
<MenuItemRow icon={<MaterialCommunityIcons name="pencil-outline" color={colors.text} size={25} />} title="Edit" onPress={() => handleViewEditKomentar()} />
|
|
<MenuItemRow icon={<MaterialIcons name="delete-outline" color={colors.text} size={25} />} title="Hapus" onPress={() => { setVisible(false); setTimeout(() => setShowDeleteModal(true), 600) }} />
|
|
</View>
|
|
</DrawerBottom>
|
|
|
|
<ModalConfirmation
|
|
visible={showDeleteModal}
|
|
title="Konfirmasi"
|
|
message="Apakah anda yakin ingin menghapus komentar?"
|
|
onConfirm={() => { setShowDeleteModal(false); handleDeleteKomentar() }}
|
|
onCancel={() => setShowDeleteModal(false)}
|
|
confirmText="Hapus"
|
|
cancelText="Batal"
|
|
/>
|
|
</>
|
|
);
|
|
}
|