diff --git a/app/(application)/discussion/[id].tsx b/app/(application)/discussion/[id].tsx
index e47e18a..bc364bf 100644
--- a/app/(application)/discussion/[id].tsx
+++ b/app/(application)/discussion/[id].tsx
@@ -1,11 +1,9 @@
import AppHeader from "@/components/AppHeader";
-import BorderBottomItem from "@/components/borderBottomItem";
import BorderBottomItem2 from "@/components/borderBottomItem2";
import HeaderRightDiscussionGeneralDetail from "@/components/discussion_general/headerDiscussionDetail";
import DrawerBottom from "@/components/drawerBottom";
import ImageUser from "@/components/imageNew";
import { InputForm } from "@/components/inputForm";
-import LabelStatus from "@/components/labelStatus";
import MenuItemRow from "@/components/menuItemRow";
import ModalConfirmation from "@/components/ModalConfirmation";
import Skeleton from "@/components/skeleton";
@@ -271,29 +269,36 @@ export default function DetailDiscussionGeneral() {
borderType="all"
bgColor="white"
icon={
-
-
+
+
}
title={data?.title}
titleShowAll={true}
subtitle={
- !data?.isActive ?
-
- :
-
+
+
+ {!data?.isActive ? 'Arsip' : data?.status == 1 ? 'Buka' : 'Tutup'}
+
+
}
desc={data?.desc}
leftBottomInfo={
-
- {dataKomentar.length} Komentar
+
+ {dataKomentar.length} Komentar
}
rightBottomInfo={
-
- {data?.createdAt}
-
+ {data?.createdAt}
}
/>
}
@@ -306,36 +311,56 @@ export default function DetailDiscussionGeneral() {
)
})
:
- dataKomentar.map((item, i) => {
- return (
-
+ dataKomentar.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',
}
- title={item.username}
- rightTopInfo={item.createdAt}
- desc={item.comment}
- rightBottomInfo={item.isEdited ? "Edited" : ""}
- descEllipsize={detailMore.includes(item.id) ? false : true}
- bgColor="white"
- onPress={() => {
- setDetailMore((prev: any) => {
- if (prev.includes(item.id)) {
- return prev.filter((id: string) => id !== item.id)
- } else {
- return [...prev, item.id]
- }
- })
- }}
- onLongPress={() => {
- item.idUser == entities.id && data?.status != 2 && data?.isActive && handleMenuKomentar(item.id, item.comment)
- }}
- />
- )
- })
+ ]}
+ >
+
+ {/* Name + time */}
+
+
+
+
+ {item.username}
+
+ {item.isEdited && (
+
+ diedit
+
+ )}
+
+
+ {item.createdAt}
+
+
+
+ {/* Comment text */}
+
+ {item.comment}
+
+
+
+ ))
}
@@ -372,15 +397,14 @@ export default function DetailDiscussionGeneral() {
multiline
focus={viewEdit}
itemRight={
- {
- (!loadingSendKomentar && selectKomentar.comment != '' && !regexOnlySpacesOrEnter.test(selectKomentar.comment) && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
- && handleEditKomentar()
- }}
- style={[
- Platform.OS == 'android' && Styles.mb12,
- ]}
+ {
+ (!loadingSendKomentar && selectKomentar.comment != '' && !regexOnlySpacesOrEnter.test(selectKomentar.comment) && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
+ && handleEditKomentar()
+ }}
+ style={[Platform.OS == 'android' && Styles.mb12]}
>
-
+
}
/>
@@ -398,15 +422,14 @@ export default function DetailDiscussionGeneral() {
multiline
focus={viewEdit}
itemRight={
- {
- (!loadingSendKomentar && komentar != '' && !regexOnlySpacesOrEnter.test(komentar) && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
- && handleKomentar()
- }}
- style={[
- Platform.OS == 'android' && Styles.mb12,
- ]}
+ {
+ (!loadingSendKomentar && komentar != '' && !regexOnlySpacesOrEnter.test(komentar) && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
+ && handleKomentar()
+ }}
+ style={[Platform.OS == 'android' && Styles.mb12]}
>
-
+
}
/>
diff --git a/app/(application)/discussion/index.tsx b/app/(application)/discussion/index.tsx
index 14b521d..c7f4bb1 100644
--- a/app/(application)/discussion/index.tsx
+++ b/app/(application)/discussion/index.tsx
@@ -1,23 +1,20 @@
-import BorderBottomItem from "@/components/borderBottomItem";
import ButtonTab from "@/components/buttonTab";
import InputSearch from "@/components/inputSearch";
import LabelStatus from "@/components/labelStatus";
import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import WrapTab from "@/components/wrapTab";
-import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneral } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
-import { AntDesign, Feather, Ionicons, MaterialIcons } from "@expo/vector-icons";
+import { AntDesign, Feather } from "@expo/vector-icons";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useMemo, useState } from "react";
-import { RefreshControl, View, VirtualizedList } from "react-native";
+import { FlatList, Pressable, RefreshControl, View } from "react-native";
import { useSelector } from "react-redux";
-
type Props = {
id: string
title: string
@@ -38,7 +35,6 @@ export default function Discussion() {
const [status, setStatus] = useState<'true' | 'false'>(active == 'false' ? 'false' : 'true')
const [refreshing, setRefreshing] = useState(false)
- // TanStack Query for Discussions with Infinite Scroll
const {
data,
fetchNextPage,
@@ -50,12 +46,12 @@ export default function Discussion() {
queryKey: ['discussions', { status, search, group }],
queryFn: async ({ pageParam = 1 }) => {
const hasil = await decryptToken(String(token?.current))
- const response = await apiGetDiscussionGeneral({
- user: hasil,
- active: status,
- search: search,
- group: String(group),
- page: pageParam
+ const response = await apiGetDiscussionGeneral({
+ user: hasil,
+ active: status,
+ search: search,
+ group: String(group),
+ page: pageParam
})
return response;
},
@@ -67,17 +63,14 @@ export default function Discussion() {
staleTime: 0,
})
- // Flatten pages into a single data array
const flatData = useMemo(() => {
return data?.pages.flatMap(page => page.data) || [];
}, [data])
- // Get nameGroup from the first available page
const nameGroup = useMemo(() => {
return data?.pages[0]?.filter?.name || "";
}, [data])
- // Refetch when manual update state changes
useEffect(() => {
refetch()
}, [update, refetch])
@@ -88,113 +81,129 @@ export default function Discussion() {
setRefreshing(false)
};
- const loadMoreData = () => {
- if (hasNextPage && !isFetchingNextPage) {
- fetchNextPage()
- }
- };
-
- const arrSkeleton = [0, 1, 2, 3, 4]
-
- const getItem = (_data: unknown, index: number): Props => ({
- id: flatData[index]?.id,
- title: flatData[index]?.title,
- desc: flatData[index]?.desc,
- status: flatData[index]?.status,
- total_komentar: flatData[index]?.total_komentar,
- createdAt: flatData[index]?.createdAt,
- })
+ const isOpen = (item: Props) => item.status === 1
return (
-
-
- {
- entityUser.role != "user" && entityUser.role != "coadmin" &&
+
+ {/* Header controls */}
+
+ {entityUser.role != "user" && entityUser.role != "coadmin" && (
{ setStatus("true") }}
+ onPress={() => setStatus("true")}
label="Aktif"
icon={}
- n={2} />
+ n={2}
+ />
{ setStatus("false") }}
+ onPress={() => setStatus("false")}
label="Arsip"
icon={}
- n={2} />
+ n={2}
+ />
- }
-
+ )}
- {
- (entityUser.role == "supadmin" || entityUser.role == "developer") &&
-
- Filter :
+ {(entityUser.role == "supadmin" || entityUser.role == "developer") && (
+
+ Filter:
- }
+ )}
-
- {
- isLoading ?
- arrSkeleton.map((item: any, i: number) => {
- return (
-
- )
- })
- :
- flatData.length > 0
- ?
- flatData.length}
- getItem={getItem}
- renderItem={({ item, index }: { item: Props, index: number }) => {
- return (
- { router.push(`/discussion/${item.id}`) }}
- borderType="bottom"
- icon={
-
- }
- title={item.title}
- subtitle={
- status != "false" &&
- }
- rightTopInfo={item.createdAt}
- desc={item.desc?.replace(/<[^>]*>?/gm, ' ').replace(/\r?\n|\r/g, ' ')}
- leftBottomInfo={
-
-
- Diskusikan
-
- }
- rightBottomInfo={`${item.total_komentar} Komentar`}
- />
- )
- }}
- keyExtractor={(item, index) => String(index)}
- onEndReached={loadMoreData}
- onEndReachedThreshold={0.5}
- showsVerticalScrollIndicator={false}
- refreshControl={
-
- }
+ {/* List */}
+
+ {isLoading ? (
+ [0, 1, 2, 3, 4].map((_, i) => )
+ ) : flatData.length === 0 ? (
+
+
+
+ Tidak ada diskusi
+
+
+ ) : (
+ String(i)}
+ showsVerticalScrollIndicator={false}
+ onEndReached={() => {
+ if (hasNextPage && !isFetchingNextPage) fetchNextPage()
+ }}
+ onEndReachedThreshold={0.5}
+ refreshControl={
+
- :
- Tidak ada data
- }
+ }
+ ItemSeparatorComponent={() => }
+ renderItem={({ item }: { item: Props }) => (
+ router.push(`/discussion/${item.id}`)}
+ style={({ pressed }) => [
+ Styles.discussionCard,
+ {
+ backgroundColor: pressed ? colors.icon + '10' : colors.card,
+ borderColor: colors.icon + '20',
+ }
+ ]}
+ >
+ {/* Top row: icon + title + status badge */}
+
+ {/* Discussion icon */}
+
+
+
+
+ {/* Title + status badge */}
+
+
+ {item.title}
+
+ {status !== "false" && (
+
+
+ {isOpen(item) ? 'Buka' : 'Tutup'}
+
+
+ )}
+
+
+
+ {/* Description */}
+ {item.desc ? (
+
+ {item.desc.replace(/<[^>]*>?/gm, ' ').replace(/\r?\n|\r/g, ' ')}
+
+ ) : null}
+
+ {/* Bottom row: comment count + date */}
+
+
+
+
+ {item.total_komentar} Komentar
+
+
+
+ {item.createdAt}
+
+
+
+ )}
+ />
+ )}
);
-}
\ No newline at end of file
+}
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 e85030f..bf1301b 100644
--- a/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/index.tsx
+++ b/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/index.tsx
@@ -1,11 +1,9 @@
import AppHeader from "@/components/AppHeader";
-import BorderBottomItem from "@/components/borderBottomItem";
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 LabelStatus from "@/components/labelStatus";
import MenuItemRow from "@/components/menuItemRow";
import ModalConfirmation from "@/components/ModalConfirmation";
import Skeleton from "@/components/skeleton";
@@ -24,7 +22,7 @@ import {
import { getDB } from "@/lib/firebaseDatabase";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
-import { Feather, Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
+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";
@@ -87,24 +85,15 @@ export default function DiscussionDetail() {
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: '', 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 })
- }
+ if (snapshot.val() == null) { reference.set({ trigger: true }) }
handleLoadComment(false)
});
-
- // Stop listening for updates when no longer required
return () => reference.off('value', onValueChange);
}, []);
@@ -115,23 +104,12 @@ export default function DiscussionDetail() {
});
}
-
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",
- });
-
+ 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);
@@ -146,11 +124,7 @@ export default function DiscussionDetail() {
try {
setLoadingKomentar(loading)
const hasil = await decryptToken(String(token?.current));
- const response = await apiGetDiscussionOne({
- id: detail,
- user: hasil,
- cat: "comment",
- });
+ const response = await apiGetDiscussionOne({ id: detail, user: hasil, cat: "comment" });
setDataComment(response.data);
} catch (error) {
console.error(error);
@@ -162,17 +136,8 @@ export default function DiscussionDetail() {
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",
- });
+ 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) {
@@ -180,33 +145,18 @@ export default function DiscussionDetail() {
}
}
- useEffect(() => {
- handleLoad(false);
- }, [update.data]);
-
- useEffect(() => {
- handleLoad(true)
- handleLoadComment(true);
- handleCheckMember();
- }, []);
+ 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()
- }
+ const response = await apiSendDiscussionCommentar({ id: detail, data: { comment: komentar, user: hasil } });
+ if (response.success) { setKomentar(""); updateTrigger() }
} catch (error: any) {
console.error(error);
- const message = error?.response?.data?.message || "Gagal menambahkan komentar"
-
- Toast.show({ type: 'small', text1: message })
+ Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal menambahkan komentar" })
} finally {
setLoadingSend(false);
}
@@ -216,20 +166,11 @@ 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 },
- });
- if (response.success) {
- updateTrigger()
- } else {
- Toast.show({ type: 'small', text1: response.message })
- }
- } catch (error : any ) {
+ 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);
- const message = error?.response?.data?.message || "Gagal mengedit komentar"
-
- Toast.show({ type: 'small', text1: message })
+ Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal mengedit komentar" })
} finally {
setLoadingSend(false);
handleViewEditKomentar()
@@ -240,20 +181,11 @@ export default function DiscussionDetail() {
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 ) {
+ 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);
- const message = error?.response?.data?.message || "Gagal menghapus komentar"
-
- Toast.show({ type: 'small', text1: message })
+ Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal menghapus komentar" })
} finally {
setLoadingSend(false)
setVisible(false)
@@ -265,13 +197,11 @@ export default function DiscussionDetail() {
setVisible(true)
}
-
function handleViewEditKomentar() {
setVisible(false)
setViewEdit(!viewEdit)
}
-
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false)
@@ -280,27 +210,15 @@ export default function DiscussionDetail() {
setRefreshing(false)
};
+ const canWrite = data?.status != 2 && data?.isActive && ((entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision)
+ const isOpen = data?.status === 1
+
return (
<>
(
- // {
- // router.back();
- // }}
- // />
- // ),
headerTitle: "Diskusi",
headerTitleAlign: "center",
- // headerRight: () =>
- // (entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision || isCreator ?
- // : (<>>)
- // ,
header: () => (
router.back()}
right={
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision || isCreator) ?
- : (<>>)
+ : undefined
}
/>
)
}}
/>
-
+
- }
+ showsVerticalScrollIndicator={false}
+ refreshControl={}
>
-
- {
- loading ?
-
- :
-
- }
- title={data?.username}
- subtitle={
- data?.isActive ? (
- data?.status == 1 ? (
-
- ) : (
-
+
+ {loading ? (
+
+ ) : (
+
+ }
+ title={data?.username}
+ titleShowAll={true}
+ subtitle={
+
+
+ {!data?.isActive ? 'Arsip' : isOpen ? 'Buka' : 'Tutup'}
+
+
+ }
+ desc={data?.desc}
+ leftBottomInfo={
+
+
+ {dataComment.length} Komentar
+
+ }
+ rightBottomInfo={{data?.createdAt}}
+ />
+ )}
+
+
+ {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]
)
- ) : (
-
- )
- }
- rightTopInfo={data?.createdAt}
- desc={data?.desc}
- leftBottomInfo={
-
-
-
- {dataComment.length} Komentar
+ }}
+ 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}
- }
- />
- }
-
-
- {
- loadingKomentar ?
- arrSkeleton.map((item, index) => (
-
- ))
- :
- dataComment.map((item, index) => (
-
- }
- title={item.username}
- rightTopInfo={item.createdAt}
- desc={item.comment}
- rightBottomInfo={item.isEdited ? "Edited" : ""}
- descEllipsize={detailMore.includes(item.id) ? false : true}
- bgColor="white"
- onPress={() => {
- setDetailMore((prev: any) => {
- if (prev.includes(item.id)) {
- return prev.filter((id: string) => id !== item.id)
- } else {
- return [...prev, item.id]
- }
- })
- }}
- onLongPress={() => {
- item.idUser == entities.id && data?.status != 2 && data?.isActive && handleMenuKomentar(item.id, 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,
- ]}
- >
-
-
- }
- />
- >
- :
- data?.status != 2 && data?.isActive && ((entityUser.role != "user" && entityUser.role != "coadmin") ||
- isMemberDivision)
- ?
- {
- 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"
- }
-
-
- }
+
+
+ {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"}
+
+
+ )}
+
- }
- title="Edit"
- onPress={() => { handleViewEditKomentar() }}
- />
- }
- title="Hapus"
- onPress={() => {
- setVisible(false)
- setTimeout(() => {
- setShowDeleteModal(true)
- }, 600)
- }}
- />
+ } title="Edit" onPress={() => handleViewEditKomentar()} />
+ } title="Hapus" onPress={() => { setVisible(false); setTimeout(() => setShowDeleteModal(true), 600) }} />
@@ -566,10 +404,7 @@ export default function DiscussionDetail() {
visible={showDeleteModal}
title="Konfirmasi"
message="Apakah anda yakin ingin menghapus komentar?"
- onConfirm={() => {
- setShowDeleteModal(false)
- handleDeleteKomentar()
- }}
+ onConfirm={() => { setShowDeleteModal(false); handleDeleteKomentar() }}
onCancel={() => setShowDeleteModal(false)}
confirmText="Hapus"
cancelText="Batal"
diff --git a/app/(application)/division/[id]/(fitur-division)/discussion/index.tsx b/app/(application)/division/[id]/(fitur-division)/discussion/index.tsx
index 43feec0..75dee0b 100644
--- a/app/(application)/division/[id]/(fitur-division)/discussion/index.tsx
+++ b/app/(application)/division/[id]/(fitur-division)/discussion/index.tsx
@@ -1,8 +1,6 @@
-import BorderBottomItem from "@/components/borderBottomItem";
import ButtonTab from "@/components/buttonTab";
import ImageUser from "@/components/imageNew";
import InputSearch from "@/components/inputSearch";
-import LabelStatus from "@/components/labelStatus";
import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import WrapTab from "@/components/wrapTab";
@@ -11,13 +9,12 @@ import Styles from "@/constants/Styles";
import { apiGetDiscussion, apiGetDivisionOneFeature } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
-import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
+import { AntDesign, Feather } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
-import { RefreshControl, View, VirtualizedList } from "react-native";
+import { FlatList, Pressable, RefreshControl, View } from "react-native";
import { useSelector } from "react-redux";
-
type Props = {
id: string,
title: string,
@@ -30,7 +27,6 @@ type Props = {
isActive: boolean
}
-
export default function DiscussionDivision() {
const { colors } = useTheme();
const { id, active } = useLocalSearchParams<{ id: string, active?: string }>()
@@ -51,17 +47,8 @@ export default function DiscussionDivision() {
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",
- });
+ 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) {
@@ -80,8 +67,6 @@ export default function DiscussionDivision() {
setData(response.data)
} else if (thisPage > 1 && response.data.length > 0) {
setData([...data, ...response.data])
- } else {
- return;
}
} catch (error) {
console.error(error)
@@ -91,26 +76,15 @@ export default function DiscussionDivision() {
}
}
- useEffect(() => {
- handleLoad(false, 1)
- }, [update.data])
-
-
- useEffect(() => {
- handleLoad(true, 1)
- }, [status, search])
+ useEffect(() => { handleLoad(false, 1) }, [update.data])
+ useEffect(() => { handleLoad(true, 1) }, [status, search])
+ useEffect(() => { handleCheckMember() }, [])
const loadMoreData = () => {
if (waiting) return
- setTimeout(() => {
- handleLoad(false, page + 1)
- }, 1000);
+ setTimeout(() => { handleLoad(false, page + 1) }, 1000);
}
- useEffect(() => {
- handleCheckMember()
- }, [])
-
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false, 1)
@@ -118,100 +92,101 @@ export default function DiscussionDivision() {
setRefreshing(false)
};
- const getItem = (_data: unknown, index: number): Props => ({
- id: data[index].id,
- title: data[index].title,
- desc: data[index].desc,
- status: data[index].status,
- user_name: data[index].user_name,
- img: data[index].img,
- total_komentar: data[index].total_komentar,
- createdAt: data[index].createdAt,
- isActive: data[index].isActive,
- })
+ const isOpen = (item: Props) => item.status === 1
return (
-
- {
- ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
-
+
+ {((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && (
+
{ setStatus("true") }}
+ onPress={() => setStatus("true")}
label="Aktif"
icon={}
- n={2} />
+ n={2}
+ />
{ setStatus("false") }}
+ onPress={() => setStatus("false")}
label="Arsip"
icon={}
- n={2} />
+ n={2}
+ />
- }
+ )}
+
+ {loading ? (
+ arrSkeleton.map((_, i) => )
+ ) : data.length === 0 ? (
+
+
+
+ Tidak ada diskusi
+
+
+ ) : (
+ String(i)}
+ showsVerticalScrollIndicator={false}
+ onEndReached={loadMoreData}
+ onEndReachedThreshold={0.5}
+ refreshControl={
+
+ }
+ ItemSeparatorComponent={() => }
+ renderItem={({ item }: { item: Props }) => (
+ router.push(`./discussion/${item.id}`)}
+ style={({ pressed }) => [
+ Styles.discussionCard,
+ { backgroundColor: pressed ? colors.icon + '10' : colors.card, borderColor: colors.icon + '20' }
+ ]}
+ >
+
+
+
+
+ {item.user_name}
+
+ {status === "true" && (
+
+
+ {isOpen(item) ? 'Buka' : 'Tutup'}
+
+
+ )}
+
+
-
- {
- loading ?
- arrSkeleton.map((item: any, i: number) => {
- return (
-
- )
- })
- :
- data.length > 0 ?
- data.length}
- getItem={getItem}
- renderItem={({ item, index }: { item: Props, index: number }) => {
- return (
- { router.push(`./discussion/${item.id}`) }}
- borderType="bottom"
- icon={
-
- }
- title={item.user_name}
- subtitle={
- status == "true" ? item.status == 1 ? : : <>>
- }
- rightTopInfo={item.createdAt}
- desc={item.desc}
- leftBottomInfo={
-
-
- Diskusikan
-
- }
- rightBottomInfo={item.total_komentar + ' Komentar'}
- bgColor="transparent"
- />
- )
- }}
- keyExtractor={(item, index) => String(index)}
- onEndReached={loadMoreData}
- onEndReachedThreshold={0.5}
- showsVerticalScrollIndicator={false}
- refreshControl={
-
- }
- />
- :
- (Tidak ada diskusi)
- }
+ {item.desc ? (
+
+ {item.desc}
+
+ ) : null}
+
+
+
+
+
+ {item.total_komentar} Komentar
+
+
+
+ {item.createdAt}
+
+
+
+ )}
+ />
+ )}
);
-}
\ No newline at end of file
+}
diff --git a/app/(application)/member/edit/[id].tsx b/app/(application)/member/edit/[id].tsx
index cb45140..f9bd5d1 100644
--- a/app/(application)/member/edit/[id].tsx
+++ b/app/(application)/member/edit/[id].tsx
@@ -171,11 +171,9 @@ export default function EditMember() {
}
function checkForm() {
- if (Object.values(error).some((v) => v == true) || Object.values(data).some((v) => v == "")) {
- setDisableBtn(true)
- } else {
- setDisableBtn(false)
- }
+ const requiredFields: (keyof Props)[] = ["idPosition", "idUserRole", "nik", "name", "email", "phone", "gender"];
+ const hasEmpty = requiredFields.some((key) => data[key] === "");
+ setDisableBtn(Object.values(error).some((v) => v === true) || hasEmpty);
}
useEffect(() => {
diff --git a/app/(application)/notification.tsx b/app/(application)/notification.tsx
index c985a6f..64322f9 100644
--- a/app/(application)/notification.tsx
+++ b/app/(application)/notification.tsx
@@ -1,18 +1,20 @@
-import BorderBottomItemVertical from "@/components/borderBottomItemVertical";
+import AppHeader from "@/components/AppHeader";
+import ModalConfirmation from "@/components/ModalConfirmation";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import Text from "@/components/Text";
-import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
-import { apiGetNotification, apiReadOneNotification } from "@/lib/api";
+import { apiGetNotification, apiReadAllNotification, apiReadOneNotification } from "@/lib/api";
import { setUpdateNotification } from "@/lib/notificationSlice";
import { pushToPage } from "@/lib/pushToPage";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { Feather } from "@expo/vector-icons";
+import { router, Stack } from "expo-router";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
-import { useEffect, useMemo, useState } from "react";
-import { RefreshControl, SafeAreaView, View, VirtualizedList } from "react-native";
+import { useEffect, useMemo } from "react";
+import { FlatList, Pressable, RefreshControl, SafeAreaView, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
+import { useState } from "react";
type Props = {
id: string
@@ -24,6 +26,22 @@ type Props = {
createdAt: string
}
+type HeaderRow = { _type: 'header'; date: string }
+type ItemRow = Props & { _type: 'item' }
+type ListRow = HeaderRow | ItemRow
+
+function getNotifStyle(category: string): { icon: keyof typeof Feather.glyphMap; color: string } {
+ if (category === 'announcement') return { icon: 'volume-2', color: '#3B82F6' }
+ if (category === 'project') return { icon: 'activity', color: '#10B981' }
+ if (category.includes('/task')) return { icon: 'clipboard', color: '#8B5CF6' }
+ if (category === 'division') return { icon: 'users', color: '#3B82F6' }
+ if (category.includes('/discussion') || category === 'discussion-general') return { icon: 'message-square', color: '#06B6D4' }
+ if (category.includes('/calendar')) return { icon: 'calendar', color: '#F59E0B' }
+ if (category.includes('/document')) return { icon: 'file-text', color: '#FBBF24' }
+ if (category === 'member') return { icon: 'user', color: '#1F3C88' }
+ return { icon: 'bell', color: '#6B7280' }
+}
+
export default function Notification() {
const { token, decryptToken } = useAuthSession()
const { colors } = useTheme();
@@ -31,8 +49,9 @@ export default function Notification() {
const dispatch = useDispatch()
const updateNotification = useSelector((state: any) => state.notificationUpdate)
const [refreshing, setRefreshing] = useState(false)
+ const [markingAll, setMarkingAll] = useState(false)
+ const [showConfirm, setShowConfirm] = useState(false)
- // TanStack Query for Notifications with Infinite Scroll
const {
data,
fetchNextPage,
@@ -55,12 +74,31 @@ export default function Notification() {
staleTime: 0,
})
- // Flatten pages into a single data array
const flatData = useMemo(() => {
return data?.pages.flatMap(page => page.data) || [];
}, [data])
- // Refetch when manual update state changes
+ const listData = useMemo(() => {
+ const groups: Record = {}
+ const dateOrder: string[] = []
+
+ flatData.forEach((item) => {
+ if (!groups[item.createdAt]) {
+ groups[item.createdAt] = []
+ dateOrder.push(item.createdAt)
+ }
+ groups[item.createdAt].push(item)
+ })
+
+ const result: ListRow[] = []
+ dateOrder.forEach((date) => {
+ result.push({ _type: 'header', date })
+ const sorted = [...groups[date]].sort((a, b) => Number(a.isRead) - Number(b.isRead))
+ sorted.forEach((item) => result.push({ ...item, _type: 'item' }))
+ })
+ return result
+ }, [flatData])
+
useEffect(() => {
refetch()
}, [updateNotification, refetch])
@@ -71,16 +109,26 @@ export default function Notification() {
setRefreshing(false)
};
- const loadMoreData = () => {
- if (hasNextPage && !isFetchingNextPage) {
- fetchNextPage()
+ const hasUnread = flatData.some((item) => !item.isRead)
+
+ async function handleReadAll() {
+ try {
+ setMarkingAll(true)
+ const hasil = await decryptToken(String(token?.current))
+ await apiReadAllNotification({ user: hasil })
+ await queryClient.invalidateQueries({ queryKey: ['notifications'] })
+ dispatch(setUpdateNotification(!updateNotification))
+ } catch (error) {
+ console.error(error)
+ } finally {
+ setMarkingAll(false)
}
- };
+ }
async function handleReadNotification(id: string, category: string, idContent: string) {
try {
const hasil = await decryptToken(String(token?.current))
- const response = await apiReadOneNotification({ user: hasil, id: id })
+ await apiReadOneNotification({ user: hasil, id: id })
await queryClient.invalidateQueries({ queryKey: ['notifications'] })
pushToPage(category, idContent)
dispatch(setUpdateNotification(!updateNotification))
@@ -89,70 +137,141 @@ export default function Notification() {
}
}
- const arrSkeleton = [0, 1, 2, 3, 4]
-
- const getItem = (_data: unknown, index: number): Props => ({
- id: flatData[index]?.id,
- title: flatData[index]?.title,
- desc: flatData[index]?.desc,
- category: flatData[index]?.category,
- idContent: flatData[index]?.idContent,
- isRead: flatData[index]?.isRead,
- createdAt: flatData[index]?.createdAt,
- });
-
return (
-
- {
- isLoading ?
- arrSkeleton.map((item, index) => {
- return (
-
- )
- })
- :
- flatData.length > 0 ?
- flatData.length}
- getItem={getItem}
- renderItem={({ item, index }: { item: Props, index: number }) => {
- return (
-
-
-
- }
- title={item.title}
- rightTopInfo={item.createdAt}
- desc={item.desc}
- textColor={item.isRead ? 'gray' : colors.text}
- onPress={() => {
- handleReadNotification(item.id, item.category, item.idContent)
- }}
- bgColor={'transparent'}
- />
- )
- }}
- keyExtractor={(item, index) => String(index)}
- onEndReached={loadMoreData}
- onEndReachedThreshold={0.5}
- showsVerticalScrollIndicator={false}
- refreshControl={
-
- }
+ (
+ router.back()}
+ right={
+ hasUnread ? (
+ setShowConfirm(true)}
+ disabled={markingAll}
+ style={{ opacity: markingAll ? 0.5 : 1, padding: 4 }}
+ >
+
+
+ ) : undefined
+ }
+ />
+ )
+ }}
+ />
+
+ {
+ setShowConfirm(false)
+ handleReadAll()
+ }}
+ onCancel={() => setShowConfirm(false)}
+ />
+
+
+ {isLoading ? (
+ [0, 1, 2, 3, 4].map((_, i) => )
+ ) : flatData.length === 0 ? (
+
+
+
+ Tidak ada notifikasi
+
+
+ ) : (
+ String(index)}
+ showsVerticalScrollIndicator={false}
+ onEndReached={() => {
+ if (hasNextPage && !isFetchingNextPage) fetchNextPage()
+ }}
+ onEndReachedThreshold={0.5}
+ refreshControl={
+
- :
- Tidak ada data
- }
+ }
+ renderItem={({ item }) => {
+ if (item._type === 'header') {
+ return (
+
+
+ {item.date}
+
+
+
+ )
+ }
+
+ const { icon, color } = getNotifStyle(item.category)
+
+ return (
+ handleReadNotification(item.id, item.category, item.idContent)}
+ style={({ pressed }) => [{
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: 10,
+ borderWidth: 1,
+ borderColor: colors.icon + '20',
+ backgroundColor: pressed
+ ? colors.icon + '10'
+ : item.isRead
+ ? colors.icon + '10'
+ : colors.card,
+ paddingHorizontal: 12,
+ paddingVertical: 10,
+ marginBottom: 6,
+ }]}
+ >
+ {/* Colored icon */}
+
+
+
+
+ {/* Content */}
+
+
+
+
+ {item.title}
+
+
+
+
+ {item.desc}
+
+
+
+ )
+ }}
+ />
+ )}
)
-}
\ No newline at end of file
+}
diff --git a/app/(application)/search.tsx b/app/(application)/search.tsx
index 6541b76..3f418f2 100644
--- a/app/(application)/search.tsx
+++ b/app/(application)/search.tsx
@@ -9,13 +9,12 @@ import Styles from "@/constants/Styles";
import { apiGetSearch } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
-import { AntDesign, MaterialIcons } from "@expo/vector-icons";
+import { AntDesign, Feather, MaterialIcons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import React, { useState } from "react";
-import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
+import { RefreshControl, SafeAreaView, ScrollView, TouchableOpacity, View } from "react-native";
import Toast from "react-native-toast-message";
-// ... types ...
type PropsUser = {
id: string
name: string
@@ -38,6 +37,27 @@ type PropDivisi = {
group: string
}
+type FilterType = "all" | "member" | "division" | "project"
+
+function SectionHeader({ label, count, colors }: { label: string; count: number; colors: any }) {
+ return (
+
+
+ {label}
+
+
+ {count}
+
+
+ )
+}
+
export default function Search() {
const { token, decryptToken } = useAuthSession()
const [dataUser, setDataUser] = useState([])
@@ -45,11 +65,16 @@ export default function Search() {
const [dataProject, setDataProject] = useState([])
const [refreshing, setRefreshing] = useState(false)
const [search, setSearch] = useState('')
+ const [activeFilter, setActiveFilter] = useState("all")
const { colors } = useTheme();
+ const totalResults = dataUser.length + dataDivisi.length + dataProject.length
+ const hasSearch = search.length >= 3
+
async function handleSearch(cari: string) {
try {
setSearch(cari)
+ setActiveFilter("all")
if (cari.length >= 3) {
const user = await decryptToken(String(token?.current))
const hasil = await apiGetSearch({ text: cari, user: user })
@@ -58,7 +83,7 @@ export default function Search() {
setDataDivisi(hasil.data.division)
setDataProject(hasil.data.project)
} else {
- return Toast.show({ type: 'small', text1: hasil.message, })
+ return Toast.show({ type: 'small', text1: hasil.message })
}
} else {
setDataUser([])
@@ -68,15 +93,10 @@ export default function Search() {
} catch (error: any) {
console.error(error);
const message = error?.response?.data?.message || "Gagal melakukan pencarian"
-
- Toast.show({
- type: 'small',
- text1: message
- })
+ Toast.show({ type: 'small', text1: message })
}
}
-
const handleRefresh = async () => {
setRefreshing(true)
handleSearch(search)
@@ -84,114 +104,203 @@ export default function Search() {
setRefreshing(false)
};
+ const filters: { key: FilterType; label: string; count: number }[] = [
+ { key: "all", label: "Semua", count: totalResults },
+ { key: "member", label: "Anggota", count: dataUser.length },
+ { key: "division", label: "Divisi", count: dataDivisi.length },
+ { key: "project", label: "Kegiatan", count: dataProject.length },
+ ]
+
+ const showUser = activeFilter === "all" || activeFilter === "member"
+ const showDivision = activeFilter === "all" || activeFilter === "division"
+ const showProject = activeFilter === "all" || activeFilter === "project"
+
+ const activeFilterEmpty =
+ (activeFilter === "member" && dataUser.length === 0) ||
+ (activeFilter === "division" && dataDivisi.length === 0) ||
+ (activeFilter === "project" && dataProject.length === 0)
return (
- <>
-
- (
- router.back()} />
- )
- }}
- />
-
+
+ (
+ router.back()} />
+ )
+ }}
+ />
+
+
+ {/* Search bar */}
+
- {
- dataProject.length + dataDivisi.length + dataUser.length > 0
- ?
-
- }
- >
- {
- dataUser.length > 0 &&
-
- ANGGOTA
- {
- dataUser.map((item, index) => (
- }
- title={item.name}
- subtitle={`${item.group}-${item.position}`}
- onPress={() => {
- router.push(`/member/${item.id}`)
- }}
- />
- ))
- }
-
- }
-
- {
- dataDivisi.length > 0 &&
-
- DIVISI
- {
- dataDivisi.map((item, index) => (
-
-
-
- }
- title={item.name}
- subtitle={item.group}
- onPress={() => {
- router.push(`/division/${item.id}`)
- }}
- />
- ))
- }
-
- }
-
-
- {
- dataProject.length > 0 &&
-
- KEGIATAN
- {
- dataProject.map((item, index) => (
-
-
-
- }
- title={item.title}
- subtitle={item.group}
- onPress={() => {
- router.push(`/project/${item.id}`)
- }}
- />
- ))
- }
-
- }
-
- :
-
- Tidak ada data
-
- }
-
-
- >
+
+ {/* Filter tabs */}
+ {hasSearch && totalResults > 0 && (
+
+
+ {filters.map((f) => {
+ const isActive = activeFilter === f.key
+ return (
+ setActiveFilter(f.key)}
+ style={{
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 6,
+ paddingHorizontal: 14,
+ borderRadius: 20,
+ marginRight: 8,
+ borderWidth: 1,
+ borderColor: isActive ? colors.tabActive : colors.icon + '40',
+ backgroundColor: isActive ? colors.tabActive + '20' : 'transparent',
+ }}
+ >
+
+ {f.label}
+
+ {f.count > 0 && (
+
+
+ {f.count}
+
+
+ )}
+
+ )
+ })}
+
+
+ )}
+
+ {/* Content */}
+
+ {!hasSearch ? (
+
+
+
+ Ketik minimal 3 karakter untuk mencari
+
+
+ ) : totalResults === 0 ? (
+
+
+
+ Tidak ada hasil untuk "{search}"
+
+
+ ) : (
+
+ }
+ >
+ {/* Anggota */}
+ {showUser && dataUser.length > 0 && (
+
+
+ {dataUser.map((item, index) => (
+
+ }
+ title={item.name}
+ subtitle={`${item.group} ยท ${item.position}`}
+ onPress={() => router.push(`/member/${item.id}`)}
+ colorPress
+ />
+
+ ))}
+
+ )}
+
+ {/* Divisi */}
+ {showDivision && dataDivisi.length > 0 && (
+
+
+ {dataDivisi.map((item, index) => (
+
+
+
+
+ }
+ title={item.name}
+ subtitle={item.group}
+ onPress={() => router.push(`/division/${item.id}`)}
+ colorPress
+ />
+
+ ))}
+
+ )}
+
+ {/* Kegiatan */}
+ {showProject && dataProject.length > 0 && (
+
+
+ {dataProject.map((item, index) => (
+
+
+
+
+ }
+ title={item.title}
+ subtitle={item.group}
+ onPress={() => router.push(`/project/${item.id}`)}
+ colorPress
+ />
+
+ ))}
+
+ )}
+
+ {/* Empty state untuk filter aktif */}
+ {activeFilter !== "all" && activeFilterEmpty && (
+
+
+
+ Tidak ada hasil di kategori ini
+
+
+ )}
+
+ )}
+
+
+
)
-}
\ No newline at end of file
+}
diff --git a/constants/Styles.ts b/constants/Styles.ts
index b9fab98..e3b2462 100644
--- a/constants/Styles.ts
+++ b/constants/Styles.ts
@@ -1113,6 +1113,62 @@ const Styles = StyleSheet.create({
flex: 1,
marginLeft: 10,
},
+ discussionCard: {
+ borderRadius: 10,
+ borderWidth: 1,
+ padding: 14,
+ },
+ discussionIconCircle: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexShrink: 0,
+ },
+ discussionIconCircleLg: {
+ width: 44,
+ height: 44,
+ borderRadius: 22,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ discussionStatusPill: {
+ alignSelf: 'flex-start',
+ marginTop: 3,
+ paddingHorizontal: 8,
+ paddingVertical: 2,
+ borderRadius: 20,
+ borderWidth: 1,
+ },
+ discussionStatusText: {
+ fontSize: 11,
+ fontWeight: '600',
+ },
+ discussionCardIndent: {
+ marginLeft: 50,
+ },
+ discussionSeparator: {
+ height: 8,
+ },
+ discussionCommentText: {
+ fontSize: 12,
+ marginLeft: 5,
+ },
+ discussionDateText: {
+ fontSize: 11,
+ },
+ discussionCommentCard: {
+ borderRadius: 10,
+ borderWidth: 1,
+ padding: 12,
+ marginBottom: 8,
+ flexDirection: 'row',
+ },
+ discussionEditedText: {
+ fontSize: 10,
+ fontStyle: 'italic',
+ },
})
export default Styles;
\ No newline at end of file
diff --git a/lib/api.ts b/lib/api.ts
index 17a3967..cf3e82a 100644
--- a/lib/api.ts
+++ b/lib/api.ts
@@ -859,6 +859,11 @@ export const apiReadOneNotification = async (data: { user: string, id: string })
return response.data;
};
+export const apiReadAllNotification = async (data: { user: string }) => {
+ const response = await api.post(`/mobile/home/notification`, data)
+ return response.data;
+};
+
export const apiGetVersion = async () => {
const response = await api.get(`mobile/version`);
return response.data;