upd: laporan kegiatan

Deskripsi
:
- tampilan list laporan pada project dan task divisi
- tampilan form update laporan pada project dan task divisi
- integrasi api update laporan pada project dan task divisi
- integrasi api view laporan pada project dan task divisi

NO Issues'
This commit is contained in:
2025-08-15 11:47:43 +08:00
parent fa5005a76a
commit 1ee9bea65e
13 changed files with 552 additions and 49 deletions

View File

@@ -67,7 +67,7 @@ export default function HeaderRightProjectDetail({ id, status }: Props) {
return (
<>
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu" height={28}>
<View style={Styles.rowItemsCenter}>
<MenuItemRow
icon={<AntDesign name="pluscircle" color="black" size={25} />}
@@ -102,29 +102,46 @@ export default function HeaderRightProjectDetail({ id, status }: Props) {
disabled={status == 3}
/>
</View>
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
<MenuItemRow
icon={<MaterialCommunityIcons name="file-document" color="black" size={25} />}
title="Laporan Kegiatan"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`/project/${id}/report`)
}}
disabled={status == 3}
/>
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<>
<MenuItemRow
icon={<MaterialIcons name="groups" color="black" size={25} />}
title="Tambah Anggota"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`/project/${id}/add-member`)
}}
disabled={status == 3}
/>
<MenuItemRow
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
title="Edit"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`/project/${id}/edit`)
}}
disabled={status == 3}
/>
</>
}
</View>
{
entityUser.role != "user" && entityUser.role != "coadmin" &&
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
<MenuItemRow
icon={<MaterialIcons name="groups" color="black" size={25} />}
title="Tambah Anggota"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`/project/${id}/add-member`)
}}
disabled={status == 3}
/>
<MenuItemRow
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
title="Edit"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`/project/${id}/edit`)
}}
disabled={status == 3}
/>
{
status == 3
?

View File

@@ -0,0 +1,55 @@
import Styles from "@/constants/Styles";
import { apiGetProjectOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { View } from "react-native";
import { useSelector } from "react-redux";
import Text from "../Text";
import TextExpandable from "../textExpandable";
export default function SectionReportProject({ refreshing }: { refreshing?: boolean }) {
const update = useSelector((state: any) => state.projectUpdate)
const { token, decryptToken } = useAuthSession();
const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState("");
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetProjectOne({
user: hasil,
cat: "data",
id: id,
});
setData(response.data.report);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad();
}, [update.report]);
useEffect(() => {
if (refreshing)
handleLoad();
}, [refreshing]);
return (
<>
{
data != "" && data != null &&
<View style={[Styles.mb15, Styles.mt10]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>
Laporan Kegiatan
</Text>
<View style={[Styles.wrapPaper]}>
<TextExpandable content={data} maxLines={2} />
</View>
</View>
}
</>
);
}

View File

@@ -101,7 +101,7 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
:
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
}
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu">
<DrawerBottom animation="slide" isVisible={isVisible} setVisible={setVisible} title="Menu" height={30}>
<View style={Styles.rowItemsCenter}>
<MenuItemRow
icon={<AntDesign name="pluscircle" color="black" size={25} />}
@@ -137,31 +137,49 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
disabled={status == 3}
/>
</View>
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
<MenuItemRow
icon={<MaterialCommunityIcons name="file-document" color="black" size={25} />}
title="Laporan Kegiatan"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`./${id}/report`)
}}
disabled={status == 3}
/>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision)
&&
<>
<MenuItemRow
icon={<MaterialIcons name="groups" color="black" size={25} />}
title="Tambah Anggota"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`./${id}/add-member`)
}}
disabled={status == 3}
/>
<MenuItemRow
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
title="Edit"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`./${id}/edit`)
}}
disabled={status == 3}
/>
</>
}
</View>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision)
&&
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
<MenuItemRow
icon={<MaterialIcons name="groups" color="black" size={25} />}
title="Tambah Anggota"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`./${id}/add-member`)
}}
disabled={status == 3}
/>
<MenuItemRow
icon={<MaterialCommunityIcons name="pencil-outline" color="black" size={25} />}
title="Edit"
onPress={() => {
if (status == 3) return
setVisible(false)
router.push(`./${id}/edit`)
}}
disabled={status == 3}
/>
{
status == 3
?
@@ -190,7 +208,7 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
}
</View>
}
</DrawerBottom>
</DrawerBottom >
<ModalFloat
title="Tambah Link"

View File

@@ -0,0 +1,53 @@
import Styles from "@/constants/Styles";
import { apiGetTaskOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { View } from "react-native";
import { useSelector } from "react-redux";
import Text from "../Text";
import TextExpandable from "../textExpandable";
export default function SectionReportTask({ refreshing }: { refreshing: boolean }) {
const update = useSelector((state: any) => state.taskUpdate)
const { token, decryptToken } = useAuthSession()
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
const [data, setData] = useState('')
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'data' })
setData(response.data.report)
} catch (error) {
console.error(error)
}
}
useEffect(() => {
handleLoad()
}, [update.report])
useEffect(() => {
if (refreshing)
handleLoad();
}, [refreshing]);
return (
<>
{
data != "" && data != null &&
<View style={[Styles.mb15, Styles.mt10]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>
Laporan Kegiatan
</Text>
<View style={[Styles.wrapPaper]}>
<TextExpandable content={data} maxLines={2} />
</View>
</View>
}
</>
)
}

View File

@@ -0,0 +1,83 @@
import Styles from "@/constants/Styles";
import { useRef, useState, useEffect } from "react";
import { Animated, Pressable, View } from "react-native";
import Text from "./Text";
export default function TextExpandable({ content, maxLines }: { content: string, maxLines: number }) {
const [isExpanded, setIsExpanded] = useState(false);
const [shouldShowMore, setShouldShowMore] = useState(false);
const [collapsedHeight, setCollapsedHeight] = useState(0);
const [fullHeight, setFullHeight] = useState(0);
const animatedHeight = useRef(new Animated.Value(0)).current;
const measureCollapsed = (e: any) => {
if (collapsedHeight === 0) {
setCollapsedHeight(e.nativeEvent.layout.height);
animatedHeight.setValue(e.nativeEvent.layout.height);
}
};
const measureFull = (e: any) => {
if (fullHeight === 0) {
setFullHeight(e.nativeEvent.layout.height);
}
};
// Cek apakah memang perlu "View More"
useEffect(() => {
if (collapsedHeight > 0 && fullHeight > 0) {
setShouldShowMore(fullHeight > collapsedHeight + 1); // +1 untuk toleransi float
}
}, [collapsedHeight, fullHeight]);
const toggleExpand = () => {
Animated.timing(animatedHeight, {
toValue: isExpanded ? collapsedHeight : fullHeight,
duration: 300,
useNativeDriver: false,
}).start();
setIsExpanded(!isExpanded);
};
return (
<View>
{/* Hidden full text for measurement */}
<View style={Styles.hidden}>
<Text style={Styles.textDefault} onLayout={measureFull}>
{content}
</Text>
</View>
{/* Collapsed text for measurement */}
<View style={Styles.hidden}>
<Text
numberOfLines={maxLines}
style={Styles.textDefault}
onLayout={measureCollapsed}
ellipsizeMode="tail"
>
{content}
</Text>
</View>
{/* Animated visible text */}
<Animated.View style={{ height: animatedHeight, overflow: 'hidden' }}>
<Text
style={Styles.textDefault}
numberOfLines={isExpanded ? undefined : maxLines}
ellipsizeMode="tail"
>
{content}
</Text>
</Animated.View>
{shouldShowMore && (
<Pressable onPress={toggleExpand}>
<Text style={Styles.textLink}>
{isExpanded ? 'View Less' : 'View More'}
</Text>
</Pressable>
)}
</View>
);
};