Compare commits

...

4 Commits

Author SHA1 Message Date
7810eb1686 upd: pake env
Deskripsi:
- ganti app.json menjadi app.config.js agar bisa pake env
- membuat env
- ganti url pake env > api url, storage url, firebase database url, otp url

No Issues
2025-08-15 17:20:23 +08:00
0956dea846 upd: tampilan
Deskripsi:
- header menu detail project
- header menu detail tugas divisi

No Issues
2025-08-15 16:25:48 +08:00
2e5698b566 fix: tampilan
Deskripsi:
- tinggi modal

No Issues
2025-08-15 11:53:46 +08:00
1ee9bea65e 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'
2025-08-15 11:47:43 +08:00
22 changed files with 645 additions and 66 deletions

3
.gitignore vendored
View File

@@ -32,6 +32,9 @@ yarn-error.*
# local env files
.env*.local
#env
.env
# typescript
*.tsbuildinfo

72
app.config.js Normal file
View File

@@ -0,0 +1,72 @@
import 'dotenv/config';
export default {
expo: {
name: "mobile-darmasaba",
slug: "mobile-darmasaba",
version: "1.0.0",
jsEngine: "jsc",
orientation: "portrait",
icon: "./assets/images/icon.png",
scheme: "myapp",
userInterfaceStyle: "automatic",
newArchEnabled: false,
ios: {
supportsTablet: true,
bundleIdentifier: "mobiledarmasaba.app",
infoPlist: {
ITSAppUsesNonExemptEncryption: false
},
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
},
android: {
package: "mobiledarmasaba.app",
adaptiveIcon: {
foregroundImage: "./assets/images/splash-icon.png",
backgroundColor: "#ffffff"
},
googleServicesFile: "./google-services.json"
},
web: {
bundler: "metro",
output: "static",
favicon: "./assets/images/favicon.png"
},
plugins: [
"expo-router",
[
"expo-splash-screen",
{
image: "./assets/images/splash-icon.png",
imageWidth: 200,
resizeMode: "contain",
backgroundColor: "#ffffff"
}
],
"expo-font",
"expo-image-picker",
"expo-web-browser",
[
"@react-native-firebase/app",
{
ios: {
googleServicesFile: "./ios/mobiledarmasaba/GoogleService-Info.plist"
}
}
]
],
experiments: {
typedRoutes: true
},
extra: {
router: {},
eas: {
projectId: "cfe34fb8-da8c-4004-b5c6-29d07df75cf2"
},
URL_API: process.env.URL_API,
URL_OTP: process.env.URL_OTP,
URL_STORAGE : process.env.URL_STORAGE,
URL_FIREBASE_DB : process.env.URL_FIREBASE_DB
}
}
};

View File

@@ -5,6 +5,7 @@ import HeaderRightTaskDetail from "@/components/task/headerTaskDetail";
import SectionFileTask from "@/components/task/sectionFileTask";
import SectionLinkTask from "@/components/task/sectionLinkTask";
import SectionMemberTask from "@/components/task/sectionMemberTask";
import SectionReportTask from "@/components/task/sectionReportTask";
import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask";
import Styles from "@/constants/Styles";
import { apiGetTaskOne } from "@/lib/api";
@@ -89,10 +90,11 @@ export default function DetailTaskDivision() {
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
}
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
<SectionTanggalTugasTask refreshing={refreshing}/>
<SectionFileTask refreshing={refreshing}/>
<SectionLinkTask refreshing={refreshing}/>
<SectionMemberTask refreshing={refreshing}/>
<SectionReportTask refreshing={refreshing} />
<SectionTanggalTugasTask refreshing={refreshing} />
<SectionFileTask refreshing={refreshing} />
<SectionLinkTask refreshing={refreshing} />
<SectionMemberTask refreshing={refreshing} />
</View>
</ScrollView>
</SafeAreaView>

View File

@@ -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>
);
}

View File

@@ -3,6 +3,7 @@ import HeaderRightProjectDetail from "@/components/project/headerProjectDetail";
import SectionFile from "@/components/project/sectionFile";
import SectionLink from "@/components/project/sectionLink";
import SectionMember from "@/components/project/sectionMember";
import SectionReportProject from "@/components/project/sectionReportProject";
import SectionTanggalTugasProject from "@/components/project/sectionTanggalTugas";
import SectionCancel from "@/components/sectionCancel";
import SectionProgress from "@/components/sectionProgress";
@@ -112,6 +113,7 @@ export default function DetailProject() {
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
}
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
<SectionReportProject refreshing={refreshing} />
<SectionTanggalTugasProject status={data?.status} member={isMember} refreshing={refreshing} />
<SectionFile status={data?.status} member={isMember} refreshing={refreshing} />
<SectionLink status={data?.status} member={isMember} refreshing={refreshing} />

View 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>
);
}

BIN
bun.lockb

Binary file not shown.

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={30}>
<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"
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

@@ -90,12 +90,6 @@ export default function SectionFile({ status, member, refreshing }: { status: nu
}
}
// async function download() {
// const destination = new Directory(Paths.document, 'pdfs');
// const filename = "dummy.pdf";
// const result = await File.downloadFileAsync('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', destination);
// }
const openFile = () => {
setModal(false)

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"
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>
);
};

View File

@@ -1,8 +1,3 @@
/**
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
*/
const tintColorLight = '#19345E';
const tintColorDark = '#fff';

5
constants/ConstEnv.ts Normal file
View File

@@ -0,0 +1,5 @@
import Constants from 'expo-constants';
export const ConstEnv = {
url_storage : Constants?.expoConfig?.extra?.URL_STORAGE
}

View File

@@ -45,8 +45,7 @@ const Styles = StyleSheet.create({
fontWeight: 'bold',
},
textLink: {
lineHeight: 30,
fontSize: 16,
fontSize: 14,
color: '#0a7ea4',
},
textInformation: {
@@ -593,6 +592,11 @@ const Styles = StyleSheet.create({
bottom: 5,
right: 5,
position: 'absolute'
},
hidden: {
position: 'absolute',
opacity: 0,
zIndex: -1,
}
})

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,11 @@
import axios from 'axios';
import Constants from 'expo-constants';
const api = axios.create({
// baseURL: 'http://10.0.2.2:3000/api',
// baseURL: 'https://stg-darmasaba.wibudev.com/api',
baseURL: 'http://192.168.1.110:3000/api',
// baseURL: 'http://192.168.154.198:3000/api',
baseURL: Constants?.expoConfig?.extra?.URL_API
});
export const apiCheckPhoneLogin = async (body: { phone: string }) => {
@@ -12,7 +14,7 @@ export const apiCheckPhoneLogin = async (body: { phone: string }) => {
}
export const apiSendOtp = async (body: { phone: string, otp: number }) => {
const res = await axios.get(`https://wa.wibudev.com/code?nom=${body.phone}&text=*DARMASABA*%0A%0A
const res = await axios.get(`${Constants.expoConfig?.extra?.URL_OTP}/code?nom=${body.phone}&text=*DARMASABA*%0A%0A
JANGAN BERIKAN KODE RAHASIA ini kepada siapa pun TERMASUK PIHAK DARMASABA. Masukkan otentikasi: *${encodeURIComponent(body.otp)}*`)
return res.status
}
@@ -274,6 +276,11 @@ export const apiEditProject = async (data: { name: string, user: string }, id: s
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 }) => {
const response = await api.post(`/mobile/project/${id}`, data)
return response.data;
@@ -584,6 +591,11 @@ export const apiEditTask = async (data: { title: string, user: string }, id: str
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) => {
const response = await api.delete(`mobile/task/${id}`, { data })
return response.data

View File

@@ -9,6 +9,7 @@ const projectUpdate = createSlice({
file: false,
member: false,
link: false,
report: false,
},
reducers: {
setUpdateProject: (state, action) => {

View File

@@ -9,6 +9,7 @@ const taskUpdate = createSlice({
file: false,
member: false,
link: false,
report: false,
},
reducers: {
setUpdateTask: (state, action) => {

View File

@@ -32,6 +32,7 @@
"crypto-es": "^2.1.0",
"crypto-js": "^3.1.9-1",
"dayjs": "^1.11.13",
"dotenv": "^17.2.1",
"expo": "^53.0.9",
"expo-blur": "~14.1.4",
"expo-clipboard": "^7.1.4",