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:
@@ -5,6 +5,7 @@ import HeaderRightTaskDetail from "@/components/task/headerTaskDetail";
|
|||||||
import SectionFileTask from "@/components/task/sectionFileTask";
|
import SectionFileTask from "@/components/task/sectionFileTask";
|
||||||
import SectionLinkTask from "@/components/task/sectionLinkTask";
|
import SectionLinkTask from "@/components/task/sectionLinkTask";
|
||||||
import SectionMemberTask from "@/components/task/sectionMemberTask";
|
import SectionMemberTask from "@/components/task/sectionMemberTask";
|
||||||
|
import SectionReportTask from "@/components/task/sectionReportTask";
|
||||||
import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask";
|
import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask";
|
||||||
import Styles from "@/constants/Styles";
|
import Styles from "@/constants/Styles";
|
||||||
import { apiGetTaskOne } from "@/lib/api";
|
import { apiGetTaskOne } from "@/lib/api";
|
||||||
@@ -89,10 +90,11 @@ export default function DetailTaskDivision() {
|
|||||||
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
|
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
|
||||||
}
|
}
|
||||||
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
|
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
|
||||||
<SectionTanggalTugasTask refreshing={refreshing}/>
|
<SectionReportTask refreshing={refreshing} />
|
||||||
<SectionFileTask refreshing={refreshing}/>
|
<SectionTanggalTugasTask refreshing={refreshing} />
|
||||||
<SectionLinkTask refreshing={refreshing}/>
|
<SectionFileTask refreshing={refreshing} />
|
||||||
<SectionMemberTask refreshing={refreshing}/>
|
<SectionLinkTask refreshing={refreshing} />
|
||||||
|
<SectionMemberTask refreshing={refreshing} />
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||||
|
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||||
|
import { InputForm } from "@/components/inputForm";
|
||||||
|
import Styles from "@/constants/Styles";
|
||||||
|
import { apiGetTaskOne, apiReportTask } from "@/lib/api";
|
||||||
|
import { setUpdateTask } from "@/lib/taskUpdate";
|
||||||
|
import { useAuthSession } from "@/providers/AuthProvider";
|
||||||
|
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { SafeAreaView, ScrollView, View } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
|
export default function TaskDivisionReport() {
|
||||||
|
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
|
||||||
|
const { token, decryptToken } = useAuthSession();
|
||||||
|
const [laporan, setLaporan] = useState("");
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [disable, setDisable] = useState(false);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const update = useSelector((state: any) => state.taskUpdate);
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
async function handleLoad() {
|
||||||
|
try {
|
||||||
|
const hasil = await decryptToken(String(token?.current));
|
||||||
|
const response = await apiGetTaskOne({
|
||||||
|
user: hasil,
|
||||||
|
cat: "data",
|
||||||
|
id: detail,
|
||||||
|
});
|
||||||
|
setLaporan(response.data.report);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleLoad();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function onValidation(val: string) {
|
||||||
|
setLaporan(val);
|
||||||
|
if (val == "" || val == "null") {
|
||||||
|
setError(true);
|
||||||
|
} else {
|
||||||
|
setError(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAll() {
|
||||||
|
if (laporan == "" || laporan == "null" || laporan == undefined || laporan == null || error) {
|
||||||
|
setDisable(true);
|
||||||
|
} else {
|
||||||
|
setDisable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkAll();
|
||||||
|
}, [laporan, error]);
|
||||||
|
|
||||||
|
async function handleUpdate() {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const hasil = await decryptToken(String(token?.current));
|
||||||
|
const response = await apiReportTask(
|
||||||
|
{
|
||||||
|
report: laporan,
|
||||||
|
user: hasil,
|
||||||
|
},
|
||||||
|
detail
|
||||||
|
);
|
||||||
|
if (response.success) {
|
||||||
|
dispatch(setUpdateTask({ ...update, report: !update.report }));
|
||||||
|
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
Toast.show({ type: 'small', text1: response.message, })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView>
|
||||||
|
<Stack.Screen
|
||||||
|
options={{
|
||||||
|
headerLeft: () => (
|
||||||
|
<ButtonBackHeader
|
||||||
|
onPress={() => {
|
||||||
|
router.back();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
headerTitle: "Laporan Kegiatan",
|
||||||
|
headerTitleAlign: "center",
|
||||||
|
headerRight: () => (
|
||||||
|
<ButtonSaveHeader
|
||||||
|
category="update"
|
||||||
|
disable={disable || loading}
|
||||||
|
onPress={() => { handleUpdate() }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ScrollView>
|
||||||
|
<View style={[Styles.p15, Styles.mb100]}>
|
||||||
|
<InputForm
|
||||||
|
label="Laporan Kegiatan"
|
||||||
|
type="default"
|
||||||
|
placeholder="Laporan Kegiatan"
|
||||||
|
required
|
||||||
|
bg="white"
|
||||||
|
value={laporan}
|
||||||
|
onChange={(val) => { onValidation(val) }}
|
||||||
|
error={error}
|
||||||
|
errorText="Laporan kegiatan harus diisi"
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import HeaderRightProjectDetail from "@/components/project/headerProjectDetail";
|
|||||||
import SectionFile from "@/components/project/sectionFile";
|
import SectionFile from "@/components/project/sectionFile";
|
||||||
import SectionLink from "@/components/project/sectionLink";
|
import SectionLink from "@/components/project/sectionLink";
|
||||||
import SectionMember from "@/components/project/sectionMember";
|
import SectionMember from "@/components/project/sectionMember";
|
||||||
|
import SectionReportProject from "@/components/project/sectionReportProject";
|
||||||
import SectionTanggalTugasProject from "@/components/project/sectionTanggalTugas";
|
import SectionTanggalTugasProject from "@/components/project/sectionTanggalTugas";
|
||||||
import SectionCancel from "@/components/sectionCancel";
|
import SectionCancel from "@/components/sectionCancel";
|
||||||
import SectionProgress from "@/components/sectionProgress";
|
import SectionProgress from "@/components/sectionProgress";
|
||||||
@@ -112,6 +113,7 @@ export default function DetailProject() {
|
|||||||
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
|
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
|
||||||
}
|
}
|
||||||
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
|
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
|
||||||
|
<SectionReportProject refreshing={refreshing} />
|
||||||
<SectionTanggalTugasProject status={data?.status} member={isMember} refreshing={refreshing} />
|
<SectionTanggalTugasProject status={data?.status} member={isMember} refreshing={refreshing} />
|
||||||
<SectionFile status={data?.status} member={isMember} refreshing={refreshing} />
|
<SectionFile status={data?.status} member={isMember} refreshing={refreshing} />
|
||||||
<SectionLink status={data?.status} member={isMember} refreshing={refreshing} />
|
<SectionLink status={data?.status} member={isMember} refreshing={refreshing} />
|
||||||
|
|||||||
128
app/(application)/project/[id]/report.tsx
Normal file
128
app/(application)/project/[id]/report.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||||
|
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||||
|
import { InputForm } from "@/components/inputForm";
|
||||||
|
import Styles from "@/constants/Styles";
|
||||||
|
import { apiGetProjectOne, apiReportProject } from "@/lib/api";
|
||||||
|
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||||
|
import { useAuthSession } from "@/providers/AuthProvider";
|
||||||
|
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { SafeAreaView, ScrollView, View } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
|
export default function ReportProject() {
|
||||||
|
const { token, decryptToken } = useAuthSession();
|
||||||
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const update = useSelector((state: any) => state.projectUpdate)
|
||||||
|
const [laporan, setLaporan] = useState("");
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [disable, setDisable] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
async function handleLoad() {
|
||||||
|
try {
|
||||||
|
const hasil = await decryptToken(String(token?.current));
|
||||||
|
const response = await apiGetProjectOne({
|
||||||
|
user: hasil,
|
||||||
|
cat: "data",
|
||||||
|
id: id,
|
||||||
|
});
|
||||||
|
setLaporan(response.data.report);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleLoad();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function onValidation(val: string) {
|
||||||
|
setLaporan(val)
|
||||||
|
if (val == "" || val == "null") {
|
||||||
|
setError(true)
|
||||||
|
} else {
|
||||||
|
setError(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAll() {
|
||||||
|
if (laporan == "" || laporan == "null" || laporan == null || laporan == undefined || error) {
|
||||||
|
setDisable(true)
|
||||||
|
} else {
|
||||||
|
setDisable(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkAll()
|
||||||
|
}, [laporan, error]);
|
||||||
|
|
||||||
|
async function handleUpdate() {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const hasil = await decryptToken(String(token?.current));
|
||||||
|
const response = await apiReportProject({
|
||||||
|
report: laporan,
|
||||||
|
user: hasil,
|
||||||
|
}, id);
|
||||||
|
if (response.success) {
|
||||||
|
dispatch(setUpdateProject({ ...update, report: !update.report }))
|
||||||
|
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
Toast.show({ type: 'small', text1: response.message, })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView>
|
||||||
|
<Stack.Screen
|
||||||
|
options={{
|
||||||
|
headerLeft: () => (
|
||||||
|
<ButtonBackHeader
|
||||||
|
onPress={() => {
|
||||||
|
router.back();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
headerTitle: "Laporan Kegiatan",
|
||||||
|
headerTitleAlign: "center",
|
||||||
|
headerRight: () => (
|
||||||
|
<ButtonSaveHeader
|
||||||
|
disable={disable || loading}
|
||||||
|
category="update"
|
||||||
|
onPress={() => { handleUpdate() }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ScrollView>
|
||||||
|
<View style={[Styles.p15, Styles.mb100]}>
|
||||||
|
<InputForm
|
||||||
|
label="Laporan Kegiatan"
|
||||||
|
type="default"
|
||||||
|
placeholder="Laporan Kegiatan"
|
||||||
|
required
|
||||||
|
bg="white"
|
||||||
|
value={laporan}
|
||||||
|
onChange={(val) => { onValidation(val) }}
|
||||||
|
error={error}
|
||||||
|
errorText="Judul Kegiatan harus diisi"
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ export default function HeaderRightProjectDetail({ id, status }: Props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonMenuHeader onPress={() => { setVisible(true) }} />
|
<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}>
|
<View style={Styles.rowItemsCenter}>
|
||||||
<MenuItemRow
|
<MenuItemRow
|
||||||
icon={<AntDesign name="pluscircle" color="black" size={25} />}
|
icon={<AntDesign name="pluscircle" color="black" size={25} />}
|
||||||
@@ -102,29 +102,46 @@ export default function HeaderRightProjectDetail({ id, status }: Props) {
|
|||||||
disabled={status == 3}
|
disabled={status == 3}
|
||||||
/>
|
/>
|
||||||
</View>
|
</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" &&
|
entityUser.role != "user" && entityUser.role != "coadmin" &&
|
||||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
<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
|
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) }} />
|
<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}>
|
<View style={Styles.rowItemsCenter}>
|
||||||
<MenuItemRow
|
<MenuItemRow
|
||||||
icon={<AntDesign name="pluscircle" color="black" size={25} />}
|
icon={<AntDesign name="pluscircle" color="black" size={25} />}
|
||||||
@@ -137,31 +137,49 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
|
|||||||
disabled={status == 3}
|
disabled={status == 3}
|
||||||
/>
|
/>
|
||||||
</View>
|
</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)
|
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision)
|
||||||
&&
|
&&
|
||||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
<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
|
status == 3
|
||||||
?
|
?
|
||||||
@@ -190,7 +208,7 @@ export default function HeaderRightTaskDetail({ id, division, status }: Props) {
|
|||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
</DrawerBottom>
|
</DrawerBottom >
|
||||||
|
|
||||||
<ModalFloat
|
<ModalFloat
|
||||||
title="Tambah Link"
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -45,8 +45,7 @@ const Styles = StyleSheet.create({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
textLink: {
|
textLink: {
|
||||||
lineHeight: 30,
|
fontSize: 14,
|
||||||
fontSize: 16,
|
|
||||||
color: '#0a7ea4',
|
color: '#0a7ea4',
|
||||||
},
|
},
|
||||||
textInformation: {
|
textInformation: {
|
||||||
@@ -593,6 +592,11 @@ const Styles = StyleSheet.create({
|
|||||||
bottom: 5,
|
bottom: 5,
|
||||||
right: 5,
|
right: 5,
|
||||||
position: 'absolute'
|
position: 'absolute'
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
position: 'absolute',
|
||||||
|
opacity: 0,
|
||||||
|
zIndex: -1,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
10
lib/api.ts
10
lib/api.ts
@@ -274,6 +274,11 @@ export const apiEditProject = async (data: { name: string, user: string }, id: s
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const apiReportProject = async (data: { report: string, user: string }, id: string) => {
|
||||||
|
const response = await api.put(`/mobile/project/${id}/lainnya`, data)
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const apiCreateProjectTask = async ({ data, id }: { data: { name: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
|
export const apiCreateProjectTask = async ({ data, id }: { data: { name: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
|
||||||
const response = await api.post(`/mobile/project/${id}`, data)
|
const response = await api.post(`/mobile/project/${id}`, data)
|
||||||
return response.data;
|
return response.data;
|
||||||
@@ -584,6 +589,11 @@ export const apiEditTask = async (data: { title: string, user: string }, id: str
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const apiReportTask = async (data: { report: string, user: string }, id: string) => {
|
||||||
|
const response = await api.put(`/mobile/task/${id}/lainnya`, data)
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const apiCancelTask = async (data: { user: string, reason: string }, id: string) => {
|
export const apiCancelTask = async (data: { user: string, reason: string }, id: string) => {
|
||||||
const response = await api.delete(`mobile/task/${id}`, { data })
|
const response = await api.delete(`mobile/task/${id}`, { data })
|
||||||
return response.data
|
return response.data
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const projectUpdate = createSlice({
|
|||||||
file: false,
|
file: false,
|
||||||
member: false,
|
member: false,
|
||||||
link: false,
|
link: false,
|
||||||
|
report: false,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setUpdateProject: (state, action) => {
|
setUpdateProject: (state, action) => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const taskUpdate = createSlice({
|
|||||||
file: false,
|
file: false,
|
||||||
member: false,
|
member: false,
|
||||||
link: false,
|
link: false,
|
||||||
|
report: false,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setUpdateTask: (state, action) => {
|
setUpdateTask: (state, action) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user