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:
@@ -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
|
||||
?
|
||||
|
||||
55
components/project/sectionReportProject.tsx
Normal file
55
components/project/sectionReportProject.tsx
Normal 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>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
53
components/task/sectionReportTask.tsx
Normal file
53
components/task/sectionReportTask.tsx
Normal 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>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
83
components/textExpandable.tsx
Normal file
83
components/textExpandable.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user