Compare commits

..

14 Commits

Author SHA1 Message Date
1dc69b64d2 Merge pull request 'amalia/21-agustus-25' (#27) from amalia/21-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#27
2025-08-21 17:37:39 +08:00
a409bfef2f upd: input datetime
Deskripsi:
- update tombol konfirmasi pada input date time ios
- dan konfigurasi pada android

No Issues
2025-08-21 17:19:39 +08:00
99c81f6f0d upd: env
Deskripsi;
- env storage
- env db firebase url

No Issues
2025-08-21 12:16:39 +08:00
4e6b27bbcc upd: env pass encrypt
Deskripsi:
- ganti env pass encripsi
- pengaplikasian env

No Issues
2025-08-21 11:35:25 +08:00
6a97ae76fc fix: date type
Deskripsi:
- fix tanggal range type data

No Issues
2025-08-21 11:08:41 +08:00
40c1fa4ed2 Merge pull request 'amalia/20-agustus-25' (#26) from amalia/20-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#26
2025-08-20 17:21:26 +08:00
e2c8f1db39 upd: fitur baru task divisi
Deskripsi;
- tampilan list detail tugas task divisi
- tampilan tambah detail tugas task divisi
- tampilan edit detail tugas task divisi
- tampilan tambah data task divisi > detail tugas
- integrasi api get data list detail tugas task divisi
- integrasi api tambah dtail tugas task divisi
- integrasi api edit detail tugas task divisi
- integrasi api tambah data task divisi > detail tugas

NO Issues'
2025-08-20 17:09:15 +08:00
72fa18565d upd: fitur baru project
Deskripsi:
- tampilan list detail tugas project
- tampilan tambah detail tugas project
- tampilan edit detail tugas project
- tampilan form tambah data project > detail tugas
- integrasi api get list detail tugas project
- integrasi api tambah detail tugas project
- integrasi api edit detail tugas project
- integrasi api tambah data project > detail tugas

No Issues
2025-08-20 15:17:10 +08:00
b0e959e3e1 Merge pull request 'amalia/19-agustus-25' (#25) from amalia/19-agustus-25 into join
Reviewed-on: bip/mobile-darmasaba#25
2025-08-19 17:42:31 +08:00
263875ae55 upd: fitur tambahan project
Deskripsi:
- tampilan list detail waktu task project
- integrasi api mobile list detail
- tampilan tambah detail task project > blm selesai

No Issues
2025-08-19 17:39:27 +08:00
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
75 changed files with 1793 additions and 276 deletions

3
.gitignore vendored
View File

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

73
app.config.js Normal file
View File

@@ -0,0 +1,73 @@
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,
PASS_ENC: process.env.PASS_ENC
}
}
};

View File

@@ -2,6 +2,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditBanner, apiGetBanner, apiGetBannerOne } from "@/lib/api";
import { setEntities } from "@/lib/bannerSlice";
@@ -50,7 +51,7 @@ export default function EditBanner() {
const hasil = await decryptToken(String(token?.current));
const data = await apiGetBannerOne({ user: hasil, id });
setSelectedImage({
uri: `https://wibu-storage.wibudev.com/api/files/${data.data.image}`,
uri: `${ConstEnv.url_storage}/files/${data.data.image}`,
});
setTitle(data.data.title);
};

View File

@@ -5,6 +5,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader"
import DrawerBottom from "@/components/drawerBottom"
import MenuItemRow from "@/components/menuItemRow"
import ModalLoading from "@/components/modalLoading"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteBanner, apiGetBanner } from "@/lib/api"
import { setEntities } from "@/lib/bannerSlice"
@@ -20,7 +21,6 @@ import * as mime from 'react-native-mime-types'
import Toast from "react-native-toast-message"
import { useDispatch, useSelector } from "react-redux"
type Props = {
id: string
title: string
@@ -71,7 +71,7 @@ export default function BannerList() {
const openFile = () => {
setModal(false)
setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.image;
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.image;
const fileName = selectFile?.title + '.' + selectFile?.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
@@ -112,7 +112,7 @@ export default function BannerList() {
}}
/>
<ModalLoading isVisible={loadingOpen} setVisible={setLoadingOpen} />
<ScrollView
<ScrollView
refreshControl={
<RefreshControl
refreshing={refreshing}
@@ -133,7 +133,7 @@ export default function BannerList() {
borderType="all"
icon={
<Image
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${index.image}` }}
source={{ uri: `${ConstEnv.url_storage}/files/${index.image}` }}
style={[Styles.imgListBanner]}
/>
}

View File

@@ -8,6 +8,7 @@ import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api";
import { getDB } from "@/lib/firebaseDatabase";
@@ -184,7 +185,7 @@ export default function DetailDiscussionGeneral() {
key={i}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="xs" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
}
title={item.username}
rightTopInfo={item.createdAt}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from '@/components/Text';
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberDiscussionGeneral, apiGetDiscussionGeneralOne, apiGetUser } from "@/lib/api";
import { setUpdateDiscussionGeneralDetail } from "@/lib/discussionGeneralDetail";
@@ -121,7 +122,7 @@ export default function AddMemberDiscussionDetail() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -147,7 +148,7 @@ export default function AddMemberDiscussionDetail() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
{
@@ -156,7 +157,7 @@ export default function AddMemberDiscussionDetail() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -7,6 +7,7 @@ import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm";
import Text from '@/components/Text';
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateDiscussionGeneral } from "@/lib/api";
import { setUpdateDiscussionGeneralDetail } from "@/lib/discussionGeneralDetail";
@@ -215,7 +216,7 @@ export default function CreateDiscussionGeneral() {
key={index}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
/>

View File

@@ -7,6 +7,7 @@ import MenuItemRow from "@/components/menuItemRow";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -109,7 +110,7 @@ export default function MemberDiscussionDetail() {
key={index}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
onPress={() => {

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberCalendar, apiGetCalendarOne, apiGetDivisionMember } from "@/lib/api";
import { setUpdateCalendar } from "@/lib/calendarUpdate";
@@ -128,7 +129,7 @@ export default function AddMemberCalendarEvent() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -154,7 +155,7 @@ export default function AddMemberCalendarEvent() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10, { width: '80%' }]}>
<Text numberOfLines={1} ellipsizeMode="tail" style={[Styles.textDefault]}>{item.name}</Text>
{
@@ -163,7 +164,7 @@ export default function AddMemberCalendarEvent() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -7,6 +7,7 @@ import ImageUser from "@/components/imageNew"
import MenuItemRow from "@/components/menuItemRow"
import Skeleton from "@/components/skeleton"
import Text from "@/components/Text"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature } from "@/lib/api"
import { setUpdateCalendar } from "@/lib/calendarUpdate"
@@ -70,7 +71,7 @@ export default function DetailEventCalendar() {
}
}
async function handleLoad(loading:boolean) {
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current));
@@ -241,7 +242,7 @@ export default function DetailEventCalendar() {
<BorderBottomItem
key={index}
borderType="bottom"
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
title={item.name}
subtitle={item.email}
onPress={() => {

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateCalendar, apiGetDivisionMember } from "@/lib/api";
import { setFormCreateCalendar } from "@/lib/calendarCreate";
@@ -117,7 +118,7 @@ export default function CreateCalendarAddMember() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -140,13 +141,13 @@ export default function CreateCalendarAddMember() {
onPress={() => { onChoose(item.idUser, item.name, item.img) }}
>
<View style={[Styles.rowItemsCenter, Styles.w70]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -7,6 +7,7 @@ import LabelStatus from "@/components/labelStatus";
import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import {
apiGetDiscussionOne,
@@ -220,7 +221,7 @@ export default function DiscussionDetail() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${data?.user_img}`}
src={`${ConstEnv.url_storage}/files/${data?.user_img}`}
size="sm"
/>
}
@@ -267,7 +268,7 @@ export default function DiscussionDetail() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
size="xs"
/>
}

View File

@@ -5,6 +5,7 @@ import InputSearch from "@/components/inputSearch";
import LabelStatus from "@/components/labelStatus";
import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDiscussion } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -141,7 +142,7 @@ export default function DiscussionDivision() {
onPress={() => { router.push(`./discussion/${item.id}`) }}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.user_name}
subtitle={
@@ -170,33 +171,8 @@ export default function DiscussionDivision() {
/>
}
/>
// data.map((item, index) => (
// <BorderBottomItem
// key={index}
// onPress={() => { router.push(`./discussion/${item.id}`) }}
// borderType="bottom"
// icon={
// <ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
// }
// title={item.user_name}
// subtitle={
// active == "true" ? item.status == 1 ? <LabelStatus category='success' text='BUKA' size="small" /> : <LabelStatus category='error' text='TUTUP' size="small" /> : <></>
// }
// rightTopInfo={item.createdAt}
// desc={item.desc}
// leftBottomInfo={
// <View style={[Styles.rowItemsCenter]}>
// <Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
// <Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
// </View>
// }
// rightBottomInfo={item.total_komentar + ' Komentar'}
// />
// ))
:
(
<Text style={[Styles.textDefault, Styles.cGray, Styles.mv10, { textAlign: "center" }]}>Tidak ada diskusi</Text>
)
(<Text style={[Styles.textDefault, Styles.cGray, Styles.mv10, { textAlign: "center" }]}>Tidak ada diskusi</Text>)
}
</View>
</View>

View File

@@ -13,6 +13,7 @@ import ModalSelectMultiple from "@/components/modalSelectMultiple";
import Skeleton from "@/components/skeleton";
import Text from "@/components/Text";
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import {
apiDocumentDelete,
@@ -268,7 +269,7 @@ export default function DocumentDivision() {
const openFile = (item: Props) => {
if (Platform.OS == 'android') setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
const fileName = item.name + '.' + item.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
@@ -440,20 +441,6 @@ export default function DocumentDivision() {
{(selectedFiles.length > 0 || dariSelectAll) && (
<View style={[ColorsStatus.primary, Styles.bottomMenuSelectDocument]}>
<View style={[Styles.rowItemsCenter, { justifyContent: "center" }]}>
{/* <MenuItemRow
icon={
<MaterialCommunityIcons
name="download-outline"
color="white"
size={25}
/>
}
title="Unduh"
onPress={() => { }}
column="many"
color="white"
disabled={selectedFiles.length == 0 || !copyAllowed}
/> */}
<MenuItemRow
icon={
<MaterialCommunityIcons

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberTask, apiGetDivisionMember, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
@@ -123,7 +124,7 @@ export default function AddMemberTask() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -149,7 +150,7 @@ export default function AddMemberTask() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w80]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
{
@@ -158,7 +159,7 @@ export default function AddMemberTask() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -1,25 +1,28 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import ModalAddDetailTugasTask from "@/components/task/modalAddDetailTugasTask";
import Text from "@/components/Text";
import Styles from "@/constants/Styles";
import { apiCreateTaskTugas } from "@/lib/api";
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import dayjs from "dayjs";
import { useHeaderHeight } from '@react-navigation/elements';
import { router, Stack, useLocalSearchParams } from "expo-router";
import 'intl';
import 'intl/locale-data/jsonp/id';
import moment from "moment";
import { useEffect, useState } from "react";
import {
KeyboardAvoidingView, Platform, SafeAreaView,
KeyboardAvoidingView, Platform, Pressable, SafeAreaView,
ScrollView,
View
} from "react-native";
import Toast from "react-native-toast-message";
import DateTimePicker, { DateType } from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
import { useHeaderHeight } from '@react-navigation/elements';
export default function TaskDivisionAddTask() {
const { token, decryptToken } = useAuthSession();
@@ -38,12 +41,13 @@ export default function TaskDivisionAddTask() {
endDate: false,
title: false,
});
const [title, setTitle] = useState("");
const [title, setTitle] = useState("")
const [dataDetail, setDataDetail] = useState<any>([])
const [modalDetail, setModalDetail] = useState(false)
const [dsbButton, setDsbButton] = useState(true)
const from = range.startDate
? dayjs(range.startDate).format("DD-MM-YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
const from = formatDateOnly(range.startDate);
const to = formatDateOnly(range.endDate);
function checkAll() {
if (
@@ -72,21 +76,49 @@ export default function TaskDivisionAddTask() {
}
}
function checkButton() {
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
setDsbButton(true)
setDataDetail([])
} else {
setDsbButton(false)
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
const datanya = getDatesInRange(awal, akhir)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
}
}
useEffect(() => {
checkAll();
}, [from, to, title, error]);
useEffect(() => {
checkButton()
}, [range])
async function handleCreate() {
try {
setLoading(true)
const dataDetailFix = dataDetail.map((item: any) => ({
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
timeStart: item.timeStart,
timeEnd: item.timeEnd,
}))
const hasil = await decryptToken(String(token?.current));
const response = await apiCreateTaskTugas({
data: {
title,
dateStart: dayjs(range.startDate).format("YYYY-MM-DD"),
dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"),
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
user: hasil,
idDivision: id,
dataDetail: dataDetailFix,
},
id: detail,
});
@@ -177,6 +209,13 @@ export default function TaskDivisionAddTask() {
{
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
}
<Pressable
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
disabled={dsbButton}
onPress={() => { setModalDetail(true) }}
>
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
</Pressable>
</View>
<InputForm
label="Judul Tugas"
@@ -194,7 +233,14 @@ export default function TaskDivisionAddTask() {
</View>
</ScrollView>
</KeyboardAvoidingView>
<ModalAddDetailTugasTask
isVisible={modalDetail}
setVisible={setModalDetail}
dataTanggal={dataDetail}
onSubmit={(data) => {
setDataDetail(data)
}}
/>
</SafeAreaView>
);
}

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

@@ -9,6 +9,7 @@ import MenuItemRow from "@/components/menuItemRow";
import ModalSelect from "@/components/modalSelect";
import SectionListAddTask from "@/components/project/sectionListAddTask";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateTask } from "@/lib/api";
import { setMemberChoose } from "@/lib/memberChoose";
@@ -189,7 +190,7 @@ export default function CreateTaskDivision() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
size="sm"
/>
}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDivisionMember } from "@/lib/api";
import { setMemberChoose } from "@/lib/memberChoose";
@@ -93,7 +94,7 @@ export default function AddMemberCreateTask() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -118,13 +119,13 @@ export default function AddMemberCreateTask() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -1,17 +1,22 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import ModalAddDetailTugasTask from "@/components/task/modalAddDetailTugasTask";
import Text from "@/components/Text";
import Styles from "@/constants/Styles";
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
import { setTaskCreate } from "@/lib/taskCreate";
import dayjs from "dayjs";
import { useHeaderHeight } from '@react-navigation/elements';
import { router, Stack } from "expo-router";
import 'intl';
import 'intl/locale-data/jsonp/id';
import moment from "moment";
import { useEffect, useState } from "react";
import {
KeyboardAvoidingView,
Platform,
Pressable,
SafeAreaView,
ScrollView,
View
@@ -20,7 +25,6 @@ import DateTimePicker, {
DateType
} from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
import { useHeaderHeight } from '@react-navigation/elements';
export default function CreateTaskAddTugas() {
const headerHeight = useHeaderHeight();
@@ -37,11 +41,12 @@ export default function CreateTaskAddTugas() {
})
const [title, setTitle] = useState('');
const taskCreate = useSelector((state: any) => state.taskCreate)
const [dsbButton, setDsbButton] = useState(true)
const [dataDetail, setDataDetail] = useState<any>([])
const [modalDetail, setModalDetail] = useState(false)
const from = range.startDate
? dayjs(range.startDate).format("DD-MM-YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
const from = formatDateOnly(range.startDate, "DD-MM-YYYY")
const to = formatDateOnly(range.endDate, "DD-MM-YYYY")
function checkAll() {
if (from == "" || to == "" || title == "" || title == "null" || error.startDate || error.endDate || error.title) {
@@ -62,18 +67,45 @@ export default function CreateTaskAddTugas() {
}
}
function checkButton() {
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
setDsbButton(true)
setDataDetail([])
} else {
setDsbButton(false)
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
const datanya = getDatesInRange(awal, akhir)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
}
}
useEffect(() => {
checkAll()
}, [from, to, title, error])
useEffect(() => {
checkButton()
}, [range])
async function handleCreate() {
try {
const dataDetailFix = dataDetail.map((item: any) => ({
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
timeStart: item.timeStart,
timeEnd: item.timeEnd,
}))
dispatch(setTaskCreate([...taskCreate, {
title: title,
dateStart: from,
dateEnd: to,
dateStartFix: dayjs(range.startDate).format("YYYY-MM-DD"),
dateEndFix: dayjs(range.endDate).format("YYYY-MM-DD"),
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
dataDetail: dataDetailFix
}]))
router.back();
} catch (error) {
@@ -151,6 +183,13 @@ export default function CreateTaskAddTugas() {
{
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
}
<Pressable
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
disabled={dsbButton}
onPress={() => { setModalDetail(true) }}
>
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
</Pressable>
</View>
<InputForm
label="Judul Tugas"
@@ -168,6 +207,14 @@ export default function CreateTaskAddTugas() {
</View>
</ScrollView>
</KeyboardAvoidingView>
<ModalAddDetailTugasTask
isVisible={modalDetail}
setVisible={setModalDetail}
dataTanggal={dataDetail}
onSubmit={(data) => {
setDataDetail(data)
}}
/>
</SafeAreaView>
);
}

View File

@@ -1,20 +1,24 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import ModalAddDetailTugasTask from "@/components/task/modalAddDetailTugasTask";
import Text from "@/components/Text";
import Styles from "@/constants/Styles";
import { apiEditTaskTugas, apiGetTaskTugas } from "@/lib/api";
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { useHeaderHeight } from '@react-navigation/elements';
import dayjs from "dayjs";
import { router, Stack, useLocalSearchParams } from "expo-router";
import 'intl';
import 'intl/locale-data/jsonp/id';
import moment from "moment";
import { useEffect, useState } from "react";
import {
KeyboardAvoidingView,
Platform,
Pressable,
SafeAreaView,
ScrollView,
View
@@ -38,6 +42,9 @@ export default function UpdateProjectTaskDivision() {
const [year, setYear] = useState<any>();
const [loading, setLoading] = useState(true);
const [disableBtn, setDisableBtn] = useState(false);
const [dataDetail, setDataDetail] = useState<any>([])
const [modalDetail, setModalDetail] = useState(false)
const [dsbButton, setDsbButton] = useState(true)
const [title, setTitle] = useState("");
const [error, setError] = useState({
startDate: false,
@@ -45,10 +52,8 @@ export default function UpdateProjectTaskDivision() {
title: false,
});
const from = range.startDate
? dayjs(range.startDate).format("DD-MM-YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
const from = formatDateOnly(range.startDate);
const to = formatDateOnly(range.endDate);
async function handleLoad() {
try {
@@ -65,6 +70,22 @@ export default function UpdateProjectTaskDivision() {
});
setMonth(new Date(response.data.dateStart).getMonth());
setYear(new Date(response.data.dateStart).getFullYear());
const response2 = await apiGetTaskTugas({
user: hasil,
id: detail,
cat: "detailTask"
});
if (response2.data.length == 0) {
const datanya = getDatesInRange(response.data.dateStart, response.data.dateEnd)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
} else {
setDataDetail(response2.data)
}
} catch (error) {
console.error(error);
} finally {
@@ -79,8 +100,22 @@ export default function UpdateProjectTaskDivision() {
async function handleEdit() {
try {
setLoadingSubmit(true)
const dataDetailFix = dataDetail.map((item: any) => ({
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
timeStart: item.timeStart,
timeEnd: item.timeEnd,
}))
const hasil = await decryptToken(String(token?.current));
const response = await apiEditTaskTugas({ data: { title, dateStart: dayjs(range.startDate).format("YYYY-MM-DD"), dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"), user: hasil }, id: detail });
const response = await apiEditTaskTugas({
data: {
title,
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
user: hasil,
dataDetail: dataDetailFix
},
id: detail
});
if (response.success) {
dispatch(setUpdateTask({ ...update, task: !update.task }))
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
@@ -123,10 +158,33 @@ export default function UpdateProjectTaskDivision() {
}
}
function checkButton() {
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
setDsbButton(true)
setDataDetail([])
} else {
setDsbButton(false)
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
const datanya = getDatesInRange(awal, akhir)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
}
}
useEffect(() => {
checkAll();
}, [from, to, title, error]);
useEffect(() => {
checkButton()
}, [range])
return (
<SafeAreaView>
<Stack.Screen
@@ -205,6 +263,13 @@ export default function UpdateProjectTaskDivision() {
Tanggal tidak boleh kosong
</Text>
)}
<Pressable
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
disabled={dsbButton}
onPress={() => { setModalDetail(true) }}
>
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
</Pressable>
</View>
<InputForm
label="Judul Tugas"
@@ -222,7 +287,14 @@ export default function UpdateProjectTaskDivision() {
</View>
</ScrollView>
</KeyboardAvoidingView>
<ModalAddDetailTugasTask
isVisible={modalDetail}
setVisible={setModalDetail}
dataTanggal={dataDetail}
onSubmit={(data) => {
setDataDetail(data)
}}
/>
</SafeAreaView>
);
}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberDivision, apiGetDivisionOneDetail, apiGetUser } from "@/lib/api";
import { setUpdateDivision } from "@/lib/divisionUpdate";
@@ -124,7 +125,7 @@ export default function AddMemberDivision() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -150,7 +151,7 @@ export default function AddMemberDivision() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
@@ -159,7 +160,7 @@ export default function AddMemberDivision() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -8,6 +8,7 @@ import SectionCancel from "@/components/sectionCancel"
import Skeleton from "@/components/skeleton"
import SkeletonTwoItem from "@/components/skeletonTwoItem"
import { ColorsStatus } from "@/constants/ColorsStatus"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiUpdateStatusAdminDivision } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
@@ -188,7 +189,7 @@ export default function InformationDivision() {
borderType="bottom"
onPress={() => { dataDetail?.isActive && handleChooseMember(item) }}
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
rightTopInfo={item.isAdmin ? "Admin" : "Anggota"}

View File

@@ -2,6 +2,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import ImageUser from "@/components/imageNew";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateDivision } from "@/lib/api";
import { setFormCreateDivision } from "@/lib/divisionCreate";
@@ -103,7 +104,7 @@ export default function CreateDivisionAddAdmin() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w70]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
@@ -112,7 +113,7 @@ export default function CreateDivisionAddAdmin() {
</View>
</View>
{
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i == item.idUser) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetUser } from "@/lib/api";
import { setFormCreateDivision } from "@/lib/divisionCreate";
@@ -86,7 +87,7 @@ export default function CreateDivisionAddMember() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -112,7 +113,7 @@ export default function CreateDivisionAddMember() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w70]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
@@ -121,7 +122,7 @@ export default function CreateDivisionAddMember() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -4,6 +4,7 @@ import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditProfile, apiGetProfile } from "@/lib/api";
import { setEntities } from "@/lib/entitiesSlice";
@@ -252,7 +253,7 @@ export default function EditProfile() {
) : (
<Pressable onPress={pickImageAsync}>
<Image
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
source={errorImg ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>

View File

@@ -5,6 +5,7 @@ import LabelStatus from "@/components/labelStatus";
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
import Skeleton from "@/components/skeleton";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import { valueRoleUser } from "@/constants/RoleUser";
import Styles from "@/constants/Styles";
import { apiGetProfile } from "@/lib/api";
@@ -93,7 +94,7 @@ export default function MemberDetail() {
</>
:
<>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${data?.img}`} size="lg" />
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" />
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10]}>{data?.name}</Text>
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{data?.role}</Text>
</>

View File

@@ -4,6 +4,7 @@ import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiEditUser, apiGetProfile } from "@/lib/api";
import { setUpdateMember } from "@/lib/memberSlice";
@@ -83,7 +84,7 @@ export default function EditMember() {
try {
const response = await apiGetProfile({ id: id });
setData(response.data);
setSelectedImage({ uri: `https://wibu-storage.wibudev.com/api/files/${response.data.img}`, });
setSelectedImage({ uri: `${ConstEnv.url_storage}/files/${response.data.img}`, });
setChoosePosition({
val: response.data.idPosition,
label: response.data.position,
@@ -270,7 +271,7 @@ export default function EditMember() {
errorImg ?
<Pressable onPress={pickImageAsync}>
<Image
source={errorImg ? require("../../../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${data?.img}` }}
source={errorImg ? require("../../../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${data?.img}` }}
style={[Styles.userProfileBig]}
onError={() => { setErrorImg(true) }}
/>

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import InputSearch from "@/components/inputSearch";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetUser } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -150,7 +151,7 @@ export default function Index() {
onPress={() => { router.push(`/member/${item.id}`) }}
borderType="all"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />
}
title={item.name}
subtitle={`${item.group} - ${item.position}`}

View File

@@ -3,6 +3,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import { ButtonHeader } from "@/components/buttonHeader";
import ItemDetailMember from "@/components/itemDetailMember";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons";
@@ -41,7 +42,7 @@ export default function Profile() {
<View style={{ flexDirection: 'column' }}>
<View style={[Styles.wrapHeadViewMember]}>
<Image
source={error ? require("../../assets/images/user.jpg") : { uri: `https://wibu-storage.wibudev.com/api/files/${entities.img}` }}
source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }}
onError={() => { setError(true) }}
style={[Styles.userProfileBig]}
/>

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiAddMemberProject, apiGetProjectOne, apiGetUser } from "@/lib/api";
import { setUpdateProject } from "@/lib/projectUpdate";
@@ -122,7 +123,7 @@ export default function AddMemberProject() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -147,7 +148,7 @@ export default function AddMemberProject() {
}}
>
<View style={[Styles.rowItemsCenter, Styles.w80]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1}>{item.name}</Text>
{
@@ -156,7 +157,7 @@ export default function AddMemberProject() {
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'}/>
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} color={'black'} />
}
</Pressable>
)

View File

@@ -1,19 +1,24 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import ModalAddDetailTugasProject from "@/components/project/modalAddDetailTugasProject";
import Text from "@/components/Text";
import Styles from "@/constants/Styles";
import { apiCreateProjectTask } from "@/lib/api";
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
import { setUpdateProject } from "@/lib/projectUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { useHeaderHeight } from '@react-navigation/elements';
import { router, Stack, useLocalSearchParams } from "expo-router";
import 'intl';
import 'intl/locale-data/jsonp/id';
import dayjs from "dayjs";
import { router, Stack, useLocalSearchParams } from "expo-router";
import moment from "moment";
import { useEffect, useState } from "react";
import {
KeyboardAvoidingView,
Platform,
Pressable,
SafeAreaView,
ScrollView,
View
@@ -23,13 +28,14 @@ import DateTimePicker, {
DateType
} from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
import { useHeaderHeight } from '@react-navigation/elements';
export default function ProjectAddTask() {
const headerHeight = useHeaderHeight();
const { token, decryptToken } = useAuthSession()
const dispatch = useDispatch()
const update = useSelector((state: any) => state.projectUpdate)
const [dataDetail, setDataDetail] = useState<any>([])
const [modalDetail, setModalDetail] = useState(false)
const { id } = useLocalSearchParams<{ id: string }>();
const [disable, setDisable] = useState(true);
const [range, setRange] = useState<{
@@ -43,11 +49,10 @@ export default function ProjectAddTask() {
})
const [title, setTitle] = useState('');
const [loading, setLoading] = useState(false)
const [dsbButton, setDsbButton] = useState(true)
const from = range.startDate
? dayjs(range.startDate).format("DD-MM-YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
const from = formatDateOnly(range.startDate);
const to = formatDateOnly(range.endDate);
function checkAll() {
if (from == "" || to == "" || title == "" || title == "null" || error.startDate || error.endDate || error.title) {
@@ -68,15 +73,50 @@ export default function ProjectAddTask() {
}
}
function checkButton() {
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
setDsbButton(true)
setDataDetail([])
} else {
setDsbButton(false)
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
const datanya = getDatesInRange(awal, akhir)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
}
}
useEffect(() => {
checkAll()
}, [from, to, title, error])
useEffect(() => {
checkButton()
}, [range])
async function handleCreate() {
try {
setLoading(true)
const dataDetailFix = dataDetail.map((item: any) => ({
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
timeStart: item.timeStart,
timeEnd: item.timeEnd,
}))
const hasil = await decryptToken(String(token?.current));
const response = await apiCreateProjectTask({ data: { name: title, dateStart: dayjs(range.startDate).format("YYYY-MM-DD"), dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"), user: hasil }, id });
const response = await apiCreateProjectTask({
data: {
name: title,
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
user: hasil,
dataDetail: dataDetailFix
}, id
});
if (response.success) {
dispatch(setUpdateProject({ ...update, task: !update.task, progress: !update.progress }))
Toast.show({ type: 'small', text1: 'Berhasil menambah data', })
@@ -125,7 +165,7 @@ export default function ProjectAddTask() {
mode="range"
startDate={range.startDate}
endDate={range.endDate}
onChange={(param) => setRange(param)}
onChange={(param) => { setRange(param) }}
styles={{
selected: Styles.selectedDate,
selected_label: Styles.cWhite,
@@ -162,6 +202,13 @@ export default function ProjectAddTask() {
{
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
}
<Pressable
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
disabled={dsbButton}
onPress={() => { setModalDetail(true) }}
>
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
</Pressable>
</View>
<InputForm
label="Judul Tugas"
@@ -179,6 +226,14 @@ export default function ProjectAddTask() {
</View>
</ScrollView>
</KeyboardAvoidingView>
<ModalAddDetailTugasProject
isVisible={modalDetail}
setVisible={setModalDetail}
dataTanggal={dataDetail}
onSubmit={(data) => {
setDataDetail(data)
}}
/>
</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>
);
}

View File

@@ -10,6 +10,7 @@ import ModalSelect from "@/components/modalSelect";
import SectionListAddTask from "@/components/project/sectionListAddTask";
import SelectForm from "@/components/selectForm";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiCreateProject } from "@/lib/api";
import { setGroupChoose } from "@/lib/groupChoose";
@@ -251,18 +252,12 @@ export default function CreateProject() {
onPress={() => {
if (entityUser.role == "supadmin" || entityUser.role == "developer") {
if (chooseGroup.val != "") {
// setSelect(true);
// setValSelect("member");
router.push(`/project/create/member`);
} else {
Toast.show({ type: 'small', text1: "Pilih Lembaga Desa terlebih dahulu", })
}
} else {
router.push(`/project/create/member`);
// validationForm('group', userLogin.idGroup, userLogin.group);
// setValChoose(userLogin.idGroup)
// setSelect(true);
// setValSelect("member");
}
}}
error={error.member}
@@ -306,7 +301,7 @@ export default function CreateProject() {
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
size="sm"
/>
}

View File

@@ -4,6 +4,7 @@ import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetUser } from "@/lib/api";
import { setMemberChoose } from "@/lib/memberChoose";
@@ -100,7 +101,7 @@ export default function AddMemberCreateProject() {
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -125,7 +126,7 @@ export default function AddMemberCreateProject() {
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
</View>

View File

@@ -1,17 +1,22 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import ModalAddDetailTugasProject from "@/components/project/modalAddDetailTugasProject";
import Text from "@/components/Text";
import Styles from "@/constants/Styles";
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
import { setTaskCreate } from "@/lib/taskCreate";
import dayjs from "dayjs";
import { useHeaderHeight } from '@react-navigation/elements';
import { router, Stack } from "expo-router";
import 'intl';
import 'intl/locale-data/jsonp/id';
import moment from "moment";
import { useEffect, useState } from "react";
import {
KeyboardAvoidingView,
Platform,
Pressable,
SafeAreaView,
ScrollView,
View
@@ -20,7 +25,6 @@ import DateTimePicker, {
DateType
} from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
import { useHeaderHeight } from '@react-navigation/elements';
export default function CreateProjectAddTask() {
const headerHeight = useHeaderHeight();
@@ -37,11 +41,12 @@ export default function CreateProjectAddTask() {
})
const [title, setTitle] = useState('');
const taskCreate = useSelector((state: any) => state.taskCreate)
const [dsbButton, setDsbButton] = useState(true)
const [dataDetail, setDataDetail] = useState<any>([])
const [modalDetail, setModalDetail] = useState(false)
const from = range.startDate
? dayjs(range.startDate).format("DD-MM-YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
const from = formatDateOnly(range.startDate, "DD-MM-YYYY");
const to = formatDateOnly(range.endDate, "DD-MM-YYYY");
function checkAll() {
if (from == "" || to == "" || title == "" || title == "null" || error.startDate || error.endDate || error.title) {
@@ -62,18 +67,45 @@ export default function CreateProjectAddTask() {
}
}
function checkButton() {
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
setDsbButton(true)
setDataDetail([])
} else {
setDsbButton(false)
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
const datanya = getDatesInRange(awal, akhir)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
}
}
useEffect(() => {
checkAll()
}, [from, to, title, error])
useEffect(() => {
checkButton()
}, [range])
async function handleCreate() {
try {
const dataDetailFix = dataDetail.map((item: any) => ({
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
timeStart: item.timeStart,
timeEnd: item.timeEnd,
}))
dispatch(setTaskCreate([...taskCreate, {
title: title,
dateStart: from,
dateEnd: to,
dateStartFix: dayjs(range.startDate).format("YYYY-MM-DD"),
dateEndFix: dayjs(range.endDate).format("YYYY-MM-DD"),
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
dataDetail: dataDetailFix
}]))
router.back();
} catch (error) {
@@ -151,6 +183,13 @@ export default function CreateProjectAddTask() {
{
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
}
<Pressable
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
disabled={dsbButton}
onPress={() => { setModalDetail(true) }}
>
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
</Pressable>
</View>
<InputForm
label="Judul Tugas"
@@ -168,6 +207,14 @@ export default function CreateProjectAddTask() {
</View>
</ScrollView>
</KeyboardAvoidingView>
<ModalAddDetailTugasProject
isVisible={modalDetail}
setVisible={setModalDetail}
dataTanggal={dataDetail}
onSubmit={(data) => {
setDataDetail(data)
}}
/>
</SafeAreaView>
);
}

View File

@@ -1,18 +1,21 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import ModalAddDetailTugasProject from "@/components/project/modalAddDetailTugasProject";
import Text from "@/components/Text";
import Styles from "@/constants/Styles";
import { apiEditProjectTask, apiGetProjectTask } from "@/lib/api";
import { formatDateOnly } from "@/lib/fun_formatDateOnly";
import { getDatesInRange } from "@/lib/fun_getDatesInRange";
import { setUpdateProject } from "@/lib/projectUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { useHeaderHeight } from '@react-navigation/elements';
import dayjs from "dayjs";
import { router, Stack, useLocalSearchParams } from "expo-router";
import 'intl';
import 'intl/locale-data/jsonp/id';
import moment from "moment";
import { useEffect, useState } from "react";
import { KeyboardAvoidingView, Platform, SafeAreaView, ScrollView, View } from "react-native";
import { KeyboardAvoidingView, Platform, Pressable, SafeAreaView, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message";
import DateTimePicker, { DateType } from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
@@ -29,6 +32,9 @@ export default function UpdateProjectTask() {
const [loading, setLoading] = useState(true)
const [disableBtn, setDisableBtn] = useState(false)
const [loadingSubmit, setLoadingSubmit] = useState(false)
const [dataDetail, setDataDetail] = useState<any>([])
const [modalDetail, setModalDetail] = useState(false)
const [dsbButton, setDsbButton] = useState(true)
const [title, setTitle] = useState('')
const [error, setError] = useState({
@@ -37,10 +43,8 @@ export default function UpdateProjectTask() {
title: false,
})
const from = range.startDate
? dayjs(range.startDate).format("DD-MM-YYYY")
: '';
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : '';
const from = formatDateOnly(range.startDate);
const to = formatDateOnly(range.endDate);
async function handleLoad() {
try {
@@ -57,6 +61,23 @@ export default function UpdateProjectTask() {
})
setMonth(new Date(response.data.dateStart).getMonth())
setYear(new Date(response.data.dateStart).getFullYear())
const response2 = await apiGetProjectTask({
user: hasil,
id: detail,
cat: "detailTask"
});
if (response2.data.length == 0) {
const datanya = getDatesInRange(response.data.dateStart, response.data.dateEnd)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
} else {
setDataDetail(response2.data)
}
} catch (error) {
console.error(error);
} finally {
@@ -71,8 +92,22 @@ export default function UpdateProjectTask() {
async function handleEdit() {
try {
setLoadingSubmit(true)
const dataDetailFix = dataDetail.map((item: any) => ({
date: moment(item.date, "DD-MM-YYYY").format("YYYY-MM-DD"),
timeStart: item.timeStart,
timeEnd: item.timeEnd,
}))
const hasil = await decryptToken(String(token?.current));
const response = await apiEditProjectTask({ data: { title, dateStart: dayjs(range.startDate).format("YYYY-MM-DD"), dateEnd: dayjs(range.endDate).format("YYYY-MM-DD"), user: hasil }, id: detail });
const response = await apiEditProjectTask({
data: {
title,
dateStart: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEnd: formatDateOnly(range.endDate, "YYYY-MM-DD"),
user: hasil,
dataDetail: dataDetailFix
},
id: detail
});
if (response.success) {
dispatch(setUpdateProject({ ...update, task: !update.task, progress: !update.progress }))
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
@@ -107,10 +142,32 @@ export default function UpdateProjectTask() {
}
}
function checkButton() {
if (range.startDate == null || range.endDate == null || range.startDate == undefined || range.endDate == undefined) {
setDsbButton(true)
setDataDetail([])
} else {
setDsbButton(false)
const awal = formatDateOnly(range.startDate, "YYYY-MM-DD")
const akhir = formatDateOnly(range.endDate, "YYYY-MM-DD")
const datanya = getDatesInRange(awal, akhir)
setDataDetail(datanya.map((item: any) => ({
date: item,
timeStart: null,
timeEnd: null,
})))
}
}
useEffect(() => {
checkAll()
}, [from, to, title, error])
useEffect(() => {
checkButton()
}, [range])
return (
<SafeAreaView>
<Stack.Screen
@@ -176,6 +233,13 @@ export default function UpdateProjectTask() {
{
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
}
<Pressable
style={[Styles.btnTab, Styles.btnLainnya, dsbButton && Styles.btnDisabled]}
disabled={dsbButton}
onPress={() => { setModalDetail(true) }}
>
<Text style={[dsbButton ? Styles.cGray : Styles.cWhite]}>Detail</Text>
</Pressable>
</View>
<InputForm
label="Judul Tugas"
@@ -193,6 +257,15 @@ export default function UpdateProjectTask() {
</View>
</ScrollView>
</KeyboardAvoidingView>
<ModalAddDetailTugasProject
isVisible={modalDetail}
setVisible={setModalDetail}
dataTanggal={dataDetail}
onSubmit={(data) => {
setDataDetail(data)
}}
/>
</SafeAreaView>
)
}

View File

@@ -3,6 +3,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import InputSearch from "@/components/inputSearch";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetSearch } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -92,7 +93,7 @@ export default function Search() {
<BorderBottomItem
borderType="bottom"
icon={<Image
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${item.img}` }}
source={{ uri: `${ConstEnv.url_storage}/files/${item.img}` }}
style={[Styles.userProfileSmall]}
/>}
title={item.name}

View File

@@ -1,5 +1,6 @@
import { ButtonForm } from "@/components/buttonForm";
import Text from '@/components/Text';
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { useAuthSession } from "@/providers/AuthProvider";
import CryptoES from "crypto-es";
@@ -19,10 +20,7 @@ export default function Index() {
const login = (): void => {
const random: string = 'contohLoginMobileDarmasaba';
var mytexttoEncryption = "contohLoginMobileDarmasaba"
const encrypted = CryptoES.AES.encrypt(mytexttoEncryption, "your password").toString();
// var C = require("crypto-js");
// var Decrypted = C.AES.decrypt(encrypted, "your password");
// var result = Decrypted.toString(C.enc.Utf8);
const encrypted = CryptoES.AES.encrypt(mytexttoEncryption, ConstEnv.pass_encrypt).toString();
signIn(encrypted);
}
return (

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetDivisionOneFeature } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
@@ -8,11 +9,11 @@ import { useLocalSearchParams } from "expo-router";
import * as Sharing from 'expo-sharing';
import React, { useEffect, useState } from "react";
import { Alert, Dimensions, Platform, Pressable, View } from "react-native";
import Text from "../Text";
import * as mime from 'react-native-mime-types';
import { ICarouselInstance } from "react-native-reanimated-carousel";
import Skeleton from "../skeleton";
import ModalLoading from "../modalLoading";
import Skeleton from "../skeleton";
import Text from "../Text";
type Props = {
id: string
@@ -52,7 +53,7 @@ export default function FileDivisionDetail() {
const openFile = (item: Props) => {
if (Platform.OS == 'android') setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + item.idStorage;
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
const fileName = item.name + '.' + item.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetBanner, apiGetProfile } from "@/lib/api";
import { setEntities } from "@/lib/bannerSlice";
@@ -50,7 +51,7 @@ export default function CaraouselHome() {
onProgressChange={progress}
renderItem={({ index }) => (
<Image
source={{ uri: `https://wibu-storage.wibudev.com/api/files/${entities[index].image}` }}
source={{ uri: `${ConstEnv.url_storage}/files/${entities[index].image}` }}
style={[Styles.caraoselContent]}
/>
)}

View File

@@ -1,8 +1,9 @@
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { stringToDate, stringToDateTime } from "@/lib/fun_stringToDate";
import DateTimePicker from "@react-native-community/datetimepicker";
import dayjs from "dayjs";
import { useState } from "react";
import { useEffect, useState } from "react";
import { Platform, Pressable, View } from "react-native";
import Text from "./Text";
import ModalFloat from "./modalFloat";
@@ -26,20 +27,50 @@ type Props = {
export function InputDate({ label, value, placeholder, onChange, info, disable, error, errorText, required, mode, round, width, }: Props) {
const [modal, setModal] = useState(false);
const [valueFix, setValueFix] = useState(new Date())
const [valueFirst, setValueFirst] = useState("")
const onChangeDate = (type: string, selectedDate: any) => {
if (type === "set") {
let formatted = ""
if (mode == "date") {
onChange(dayjs(selectedDate).format("DD-MM-YYYY"))
formatted = dayjs(selectedDate).format("DD-MM-YYYY")
} else if (mode == "time") {
onChange(dayjs(selectedDate).format("HH:mm"))
formatted = dayjs(selectedDate).format("HH:mm")
}
setValueFirst(formatted)
if (Platform.OS == "android") {
onChange(formatted)
setModal(false)
}
setModal(false)
} else {
setModal(false);
}
};
function onSetValue() {
onChange(valueFirst)
setModal(false)
}
function changeValue() {
if (value) {
let valDate = new Date()
if (mode == "date") {
valDate = stringToDate(value)
} else if (mode == "time") {
valDate = stringToDateTime("", value)
}
setValueFix(valDate)
}
}
useEffect(() => {
if (modal) changeValue()
}, [value, modal])
return (
<>
<View style={[Styles.mb10]}>
@@ -63,23 +94,24 @@ export function InputDate({ label, value, placeholder, onChange, info, disable,
<ModalFloat
isVisible={modal}
setVisible={setModal}
onSubmit={() => { }}
buttonHide
disableSubmit
onSubmit={() => { onSetValue() }}
title={mode == "date" ? "Pilih Tanggal" : mode == "time" ? "Pilih Jam" : "Pilih Tanggal & Jam"}>
<DateTimePicker
value={new Date()}
value={valueFix}
mode={mode}
display="spinner"
onChange={(event, date) => { onChangeDate(event.type, date) }}
onChange={(event, date) => {
onChangeDate(event.type, date)
}}
onTouchCancel={() => setModal(false)}
/>
</ModalFloat>
)
) : (
modal && (
<DateTimePicker
value={new Date()}
value={valueFix}
mode={mode}
display="inline"
onChange={(event, date) => { onChangeDate(event.type, date) }}

View File

@@ -8,7 +8,7 @@ type Props = {
setVisible: (value: boolean) => void
title?: string
children: React.ReactNode
onSubmit: () => void
onSubmit?: () => void
disableSubmit?: boolean
buttonHide?: boolean
}
@@ -33,10 +33,10 @@ export default function ModalFloat({ isVisible, setVisible, title, children, onS
!buttonHide && (
<View style={[Styles.rowItemsCenter, { justifyContent: 'flex-end' }]}>
<Pressable style={[Styles.ph15, Styles.pv05, Styles.round10, Styles.mr10]} onPress={() => { setVisible(false) }}>
<Text style={[Styles.textDefault]}>Batal</Text>
<Text style={[Styles.textDefaultSemiBold]}>Batal</Text>
</Pressable>
<Pressable style={[Styles.ph15, Styles.pv05, Styles.round10]} onPress={onSubmit} disabled={disableSubmit}>
<Text style={[Styles.textDefault, disableSubmit && Styles.cGray]}>Simpan</Text>
<Text style={[Styles.textDefaultSemiBold, disableSubmit && Styles.cGray]}>Simpan</Text>
</Pressable>
</View>
)

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv"
import { valueGender } from "@/constants/Gender"
import { valueRoleUser } from "@/constants/RoleUser"
import Styles from "@/constants/Styles"
@@ -140,7 +141,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
src={`${ConstEnv.url_storage}/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
@@ -164,7 +165,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
category == 'member'
?
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} border />
<Text style={[Styles.textDefault, Styles.ml10]}>{item.name}</Text>
</View>
:

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

@@ -0,0 +1,135 @@
import Styles from "@/constants/Styles";
import { stringToDateTime } from "@/lib/fun_stringToDate";
import { useEffect, useState } from "react";
import { Dimensions, View, VirtualizedList } from "react-native";
import { InputDate } from "../inputDate";
import ModalFloat from "../modalFloat";
import Text from "../Text";
interface Props {
date: string;
timeStart: string;
timeEnd: string;
}
export default function ModalAddDetailTugasProject({ isVisible, setVisible, dataTanggal, onSubmit }: { isVisible: boolean, setVisible: (value: boolean) => void, dataTanggal: Props[], onSubmit: (data: Props[]) => void }) {
const [data, setData] = useState<Props[]>(dataTanggal)
const tinggiScreen = Dimensions.get("window").height;
const tinggiFix = tinggiScreen * 70 / 100;
const [error, setError] = useState<any>([])
useEffect(() => {
if (isVisible) {
setData(dataTanggal)
setError([])
}
}, [isVisible, dataTanggal])
const getItem = (_data: unknown, index: number): Props => ({
date: data[index].date,
timeStart: data[index].timeStart,
timeEnd: data[index].timeEnd,
})
function settingError(date: string, cat: 'timeStart' | 'timeEnd', val: boolean) {
const ada = error.find((item: any) => item.date == date)
if (ada) {
setError(error.map((item: any) => {
if (item.date == date) {
return { ...item, [cat]: val }
}
return item
}))
} else {
setError([...error, { date, [cat]: val }])
}
}
function validationForm(cat: "timeStart" | "timeEnd", val: string, date: string) {
if (cat == "timeEnd") {
const start = stringToDateTime("", String(data.find((item) => item.date == date)?.timeStart))
const end = stringToDateTime("", val)
const timestampAwal = start.getTime()
const timestampAkhir = end.getTime()
if (val == "" || val == null || timestampAwal > timestampAkhir) {
settingError(date, "timeEnd", true)
} else {
settingError(date, "timeEnd", false)
}
} else {
const end = stringToDateTime("", String(data.find((item) => item.date == date)?.timeEnd))
const start = stringToDateTime("", val)
const timestampAwal = start.getTime()
const timestampAkhir = end.getTime()
if (val == "" || val == null || timestampAwal > timestampAkhir) {
settingError(date, "timeEnd", true)
} else {
settingError(date, "timeEnd", false)
}
}
setData(data.map((item) => {
if (item.date == date) {
return { ...item, [cat]: val }
}
return item
}))
}
return (
<ModalFloat
title="Detail Tanggal dan Waktu Tugas"
isVisible={isVisible}
setVisible={setVisible}
disableSubmit={Object.values(error).some((val: any) => val.timeEnd == true || val.timeStart == true)}
onSubmit={() => {
onSubmit(data)
setVisible(false)
}}
>
<View style={[{ height: tinggiFix }]} >
<VirtualizedList
data={data}
getItemCount={() => data.length}
getItem={getItem}
renderItem={({ item, index }: { item: Props, index: number }) => {
return (
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
<View style={[Styles.rowSpaceBetween]}>
<View style={[{ width: "48%" }]}>
<InputDate
mode="time"
onChange={(val) => { validationForm("timeStart", val, item.date) }}
value={item.timeStart}
label="Waktu Awal"
placeholder="--:--"
error={error.find((error: any) => error.date == item.date)?.timeStart}
errorText="Waktu awal tidak valid"
/>
</View>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => { validationForm("timeEnd", val, item.date) }}
mode="time"
value={item.timeEnd}
label="Waktu Akhir"
placeholder="--:--"
error={error.find((error: any) => error.date == item.date)?.timeEnd}
errorText="Waktu akhir harus lebih dari waktu awal"
/>
</View>
</View>
</View>
)
}}
keyExtractor={(item, index) => String(index)}
showsVerticalScrollIndicator={false}
/>
</View>
</ModalFloat>
)
}

View File

@@ -0,0 +1,116 @@
import Styles from "@/constants/Styles";
import { apiGetProjectTask } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useEffect, useState } from "react";
import { Dimensions, View, VirtualizedList } from "react-native";
import { InputDate } from "../inputDate";
import ModalFloat from "../modalFloat";
import Skeleton from "../skeleton";
import Text from "../Text";
interface Props {
id: string;
date: string;
timeStart: string;
timeEnd: string;
}
export default function ModalListDetailTugasProject({ isVisible, setVisible, idTask }: { isVisible: boolean, setVisible: (value: boolean) => void, idTask: string }) {
const [data, setData] = useState<Props[]>([])
const [loading, setLoading] = useState(false)
const { token, decryptToken } = useAuthSession()
const arrSkeleton = Array.from({ length: 3 }, (_, index) => index)
const tinggiScreen = Dimensions.get("window").height;
const tinggiFix = tinggiScreen * 70 / 100;
async function getData() {
try {
setLoading(true)
const hasil = await decryptToken(String(token?.current));
const res = await apiGetProjectTask({ user: hasil, id: idTask, cat: "detailTask" })
setData(res.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (isVisible) {
getData()
}
}, [isVisible, idTask])
const getItem = (_data: unknown, index: number): Props => ({
id: data[index].id,
date: data[index].date,
timeStart: data[index].timeStart,
timeEnd: data[index].timeEnd,
})
return (
<ModalFloat
title="Detail Tanggal dan Waktu Tugas"
isVisible={isVisible}
setVisible={setVisible}
buttonHide
>
<View style={[{ height: tinggiFix }]} >
{
loading ?
arrSkeleton.map((item: any, i: number) => {
return (
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
)
})
:
data.length > 0 ?
(
<VirtualizedList
data={data}
getItemCount={() => data.length}
getItem={getItem}
renderItem={({ item, index }: { item: Props, index: number }) => {
return (
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
<View style={[Styles.rowSpaceBetween]}>
<View style={[{ width: "48%" }]}>
<InputDate
mode="time"
disable
onChange={(val) => { }}
value={item.timeStart}
label="Waktu Awal"
placeholder="--:--"
/>
</View>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => { }}
mode="time"
value={item.timeEnd}
label="Waktu Akhir"
placeholder="--:--"
disable
/>
</View>
</View>
</View>
)
}}
keyExtractor={(item, index) => String(index)}
showsVerticalScrollIndicator={false}
/>
)
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]} >Tidak ada data</Text>
}
</View>
</ModalFloat>
)
}

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiDeleteFileProject, apiGetProjectOne } from "@/lib/api";
import { setUpdateProject } from "@/lib/projectUpdate";
@@ -90,17 +91,11 @@ 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)
setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.idStorage;
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.idStorage;
const fileName = selectFile?.name + '.' + selectFile?.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
@@ -178,14 +173,6 @@ export default function SectionFile({ status, member, refreshing }: { status: nu
openFile()
}}
/>
{/* <MenuItemRow
icon={<MaterialCommunityIcons name="download" color="black" size={25} />}
title="Download"
onPress={() => {
// download()
// setModal(false)
}}
/> */}
{
!member && (entityUser.role == "user" || entityUser.role == "coadmin") ? <></>
:

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiDeleteProjectMember, apiGetProjectOne } from "@/lib/api";
import { setUpdateProject } from "@/lib/projectUpdate";
@@ -112,7 +113,7 @@ export default function SectionMember({ status, refreshing }: { status: number |
<BorderBottomItem
key={index}
borderType="bottom"
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
title={item.name}
onPress={() => {
if (status == 3) return

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

@@ -15,6 +15,7 @@ import MenuItemRow from "../menuItemRow";
import ModalSelect from "../modalSelect";
import SkeletonTask from "../skeletonTask";
import Text from "../Text";
import ModalListDetailTugasProject from "./modalListDetailTugasProject";
type Props = {
id: string;
@@ -33,6 +34,7 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
const [isModal, setModal] = useState(false);
const [isSelect, setSelect] = useState(false);
const { token, decryptToken } = useAuthSession();
const [modalDetail, setModalDetail] = useState(false)
const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState<Props[]>([]);
const [loading, setLoading] = useState(true)
@@ -188,6 +190,24 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
}}
/>
<MenuItemRow
icon={
<MaterialCommunityIcons
name="clock-time-three-outline"
color="black"
size={25}
/>
}
title="Detail Waktu"
onPress={() => {
setModal(false);
setTimeout(() => {
setModalDetail(true)
}, 600)
}}
/>
</View>
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
<MenuItemRow
icon={<Ionicons name="trash" color="black" size={25} />}
title="Hapus Tugas"
@@ -213,6 +233,12 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
open={isSelect}
valChoose={String(tugas.status)}
/>
<ModalListDetailTugasProject
isVisible={modalDetail}
setVisible={setModalDetail}
idTask={tugas.id}
/>
</>
);
}

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,135 @@
import Styles from "@/constants/Styles";
import { stringToDateTime } from "@/lib/fun_stringToDate";
import { useEffect, useState } from "react";
import { Dimensions, View, VirtualizedList } from "react-native";
import { InputDate } from "../inputDate";
import ModalFloat from "../modalFloat";
import Text from "../Text";
interface Props {
date: string;
timeStart: string;
timeEnd: string;
}
export default function ModalAddDetailTugasTask({ isVisible, setVisible, dataTanggal, onSubmit }: { isVisible: boolean, setVisible: (value: boolean) => void, dataTanggal: Props[], onSubmit: (data: Props[]) => void }) {
const [data, setData] = useState<Props[]>(dataTanggal)
const tinggiScreen = Dimensions.get("window").height;
const tinggiFix = tinggiScreen * 70 / 100;
const [error, setError] = useState<any>([])
useEffect(() => {
if (isVisible) {
setData(dataTanggal)
setError([])
}
}, [isVisible, dataTanggal])
const getItem = (_data: unknown, index: number): Props => ({
date: data[index].date,
timeStart: data[index].timeStart,
timeEnd: data[index].timeEnd,
})
function settingError(date: string, cat: 'timeStart' | 'timeEnd', val: boolean) {
const ada = error.find((item: any) => item.date == date)
if (ada) {
setError(error.map((item: any) => {
if (item.date == date) {
return { ...item, [cat]: val }
}
return item
}))
} else {
setError([...error, { date, [cat]: val }])
}
}
function validationForm(cat: "timeStart" | "timeEnd", val: string, date: string) {
if (cat == "timeEnd") {
const start = stringToDateTime("", String(data.find((item) => item.date == date)?.timeStart))
const end = stringToDateTime("", val)
const timestampAwal = start.getTime()
const timestampAkhir = end.getTime()
if (val == "" || val == null || timestampAwal > timestampAkhir) {
settingError(date, "timeEnd", true)
} else {
settingError(date, "timeEnd", false)
}
} else {
const end = stringToDateTime("", String(data.find((item) => item.date == date)?.timeEnd))
const start = stringToDateTime("", val)
const timestampAwal = start.getTime()
const timestampAkhir = end.getTime()
if (val == "" || val == null || timestampAwal > timestampAkhir) {
settingError(date, "timeEnd", true)
} else {
settingError(date, "timeEnd", false)
}
}
setData(data.map((item) => {
if (item.date == date) {
return { ...item, [cat]: val }
}
return item
}))
}
return (
<ModalFloat
title="Detail Tanggal dan Waktu Tugas"
isVisible={isVisible}
setVisible={setVisible}
disableSubmit={Object.values(error).some((val: any) => val.timeEnd == true || val.timeStart == true)}
onSubmit={() => {
onSubmit(data)
setVisible(false)
}}
>
<View style={[{ height: tinggiFix }]} >
<VirtualizedList
data={data}
getItemCount={() => data.length}
getItem={getItem}
renderItem={({ item, index }: { item: Props, index: number }) => {
return (
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
<View style={[Styles.rowSpaceBetween]}>
<View style={[{ width: "48%" }]}>
<InputDate
mode="time"
onChange={(val) => { validationForm("timeStart", val, item.date) }}
value={item.timeStart}
label="Waktu Awal"
placeholder="--:--"
error={error.find((error: any) => error.date == item.date)?.timeStart}
errorText="Waktu awal tidak valid"
/>
</View>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => { validationForm("timeEnd", val, item.date) }}
mode="time"
value={item.timeEnd}
label="Waktu Akhir"
placeholder="--:--"
error={error.find((error: any) => error.date == item.date)?.timeEnd}
errorText="Waktu akhir harus lebih dari waktu awal"
/>
</View>
</View>
</View>
)
}}
keyExtractor={(item, index) => String(index)}
showsVerticalScrollIndicator={false}
/>
</View>
</ModalFloat>
)
}

View File

@@ -0,0 +1,116 @@
import Styles from "@/constants/Styles";
import { apiGetProjectTask, apiGetTaskTugas } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useEffect, useState } from "react";
import { Dimensions, View, VirtualizedList } from "react-native";
import { InputDate } from "../inputDate";
import ModalFloat from "../modalFloat";
import Skeleton from "../skeleton";
import Text from "../Text";
interface Props {
id: string;
date: string;
timeStart: string;
timeEnd: string;
}
export default function ModalListDetailTugasTask({ isVisible, setVisible, idTask }: { isVisible: boolean, setVisible: (value: boolean) => void, idTask: string }) {
const [data, setData] = useState<Props[]>([])
const [loading, setLoading] = useState(false)
const { token, decryptToken } = useAuthSession()
const arrSkeleton = Array.from({ length: 3 }, (_, index) => index)
const tinggiScreen = Dimensions.get("window").height;
const tinggiFix = tinggiScreen * 70 / 100;
async function getData() {
try {
setLoading(true)
const hasil = await decryptToken(String(token?.current));
const res = await apiGetTaskTugas({ user: hasil, id: idTask, cat: "detailTask" })
setData(res.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (isVisible) {
getData()
}
}, [isVisible, idTask])
const getItem = (_data: unknown, index: number): Props => ({
id: data[index].id,
date: data[index].date,
timeStart: data[index].timeStart,
timeEnd: data[index].timeEnd,
})
return (
<ModalFloat
title="Detail Tanggal dan Waktu Tugas"
isVisible={isVisible}
setVisible={setVisible}
buttonHide
>
<View style={[{ height: tinggiFix }]} >
{
loading ?
arrSkeleton.map((item: any, i: number) => {
return (
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
)
})
:
data.length > 0 ?
(
<VirtualizedList
data={data}
getItemCount={() => data.length}
getItem={getItem}
renderItem={({ item, index }: { item: Props, index: number }) => {
return (
<View key={index} style={[Styles.borderBottom, Styles.pv05]}>
<Text style={[Styles.textDefaultSemiBold]}>{item.date}</Text>
<View style={[Styles.rowSpaceBetween]}>
<View style={[{ width: "48%" }]}>
<InputDate
mode="time"
disable
onChange={(val) => { }}
value={item.timeStart}
label="Waktu Awal"
placeholder="--:--"
/>
</View>
<View style={[{ width: "48%" }]}>
<InputDate
onChange={(val) => { }}
mode="time"
value={item.timeEnd}
label="Waktu Akhir"
placeholder="--:--"
disable
/>
</View>
</View>
</View>
)
}}
keyExtractor={(item, index) => String(index)}
showsVerticalScrollIndicator={false}
/>
)
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]} >Tidak ada data</Text>
}
</View>
</ModalFloat>
)
}

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiDeleteFileTask, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
@@ -27,7 +28,7 @@ type Props = {
idStorage: string
}
export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
export default function SectionFileTask({ refreshing }: { refreshing: boolean }) {
const [isModal, setModal] = useState(false)
const { token, decryptToken } = useAuthSession()
const { detail } = useLocalSearchParams<{ detail: string }>()
@@ -68,7 +69,7 @@ export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
const openFile = () => {
setModal(false)
setLoadingOpen(true)
let remoteUrl = 'https://wibu-storage.wibudev.com/api/files/' + selectFile?.idStorage;
let remoteUrl = ConstEnv.url_storage + '/files/' + selectFile?.idStorage;
const fileName = selectFile?.name + '.' + selectFile?.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
@@ -160,17 +161,8 @@ export default function SectionFileTask({refreshing}: {refreshing: boolean}) {
title="Lihat / Share"
onPress={() => {
openFile()
// setModal(false)
}}
/>
{/* <MenuItemRow
icon={<MaterialCommunityIcons name="download" color="black" size={25} />}
title="Download"
onPress={() => {
setModal(false)
}}
/> */}
<MenuItemRow
icon={<Ionicons name="trash" color="black" size={25} />}
title="Hapus"

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiDeleteTaskMember, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
@@ -114,9 +115,7 @@ export default function SectionMemberTask({ refreshing }: { refreshing: boolean
key={index}
borderType="bottom"
icon={
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
/>
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />
}
title={item.name}
onPress={() => {

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

@@ -15,6 +15,7 @@ import MenuItemRow from "../menuItemRow";
import ModalSelect from "../modalSelect";
import SkeletonTask from "../skeletonTask";
import Text from "../Text";
import ModalListDetailTugasTask from "./modalListDetailTugasTask";
type Props = {
@@ -25,7 +26,7 @@ type Props = {
dateEnd: string;
}
export default function SectionTanggalTugasTask({refreshing}: {refreshing: boolean}) {
export default function SectionTanggalTugasTask({ refreshing }: { refreshing: boolean }) {
const dispatch = useDispatch()
const update = useSelector((state: any) => state.taskUpdate)
const [isModal, setModal] = useState(false)
@@ -33,6 +34,7 @@ export default function SectionTanggalTugasTask({refreshing}: {refreshing: boole
const { token, decryptToken } = useAuthSession()
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 5 })
const [modalDetail, setModalDetail] = useState(false)
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
const [data, setData] = useState<Props[]>([])
const [tugas, setTugas] = useState({
@@ -171,7 +173,24 @@ export default function SectionTanggalTugasTask({refreshing}: {refreshing: boole
router.push(`./update/${tugas.id}`)
}}
/>
<MenuItemRow
icon={
<MaterialCommunityIcons
name="clock-time-three-outline"
color="black"
size={25}
/>
}
title="Detail Waktu"
onPress={() => {
setModal(false);
setTimeout(() => {
setModalDetail(true)
}, 600)
}}
/>
</View>
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
<MenuItemRow
icon={<Ionicons name="trash" color="black" size={25} />}
title="Hapus Tugas"
@@ -198,6 +217,12 @@ export default function SectionTanggalTugasTask({refreshing}: {refreshing: boole
open={isSelect}
valChoose={String(tugas.status)}
/>
<ModalListDetailTugasTask
isVisible={modalDetail}
setVisible={setModalDetail}
idTask={tugas.id}
/>
</>
)
}

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';

6
constants/ConstEnv.ts Normal file
View File

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

View File

@@ -45,8 +45,7 @@ const Styles = StyleSheet.create({
fontWeight: 'bold',
},
textLink: {
lineHeight: 30,
fontSize: 16,
fontSize: 14,
color: '#0a7ea4',
},
textInformation: {
@@ -270,6 +269,15 @@ const Styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'center'
},
btnLainnya: {
alignSelf: 'flex-start',
backgroundColor: '#19345E',
paddingVertical: 5,
marginVertical: 5
},
btnDisabled: {
backgroundColor: '#d6d8f6',
},
btnMenuRow: {
width: '33%',
alignItems: 'center'
@@ -593,6 +601,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,7 +276,12 @@ export const apiEditProject = async (data: { name: string, user: string }, id: s
return response.data;
};
export const apiCreateProjectTask = async ({ data, id }: { data: { name: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
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, dataDetail: any[] }, id: string }) => {
const response = await api.post(`/mobile/project/${id}`, data)
return response.data;
};
@@ -294,12 +301,12 @@ export const apiDeleteProjectTask = async (data: { user: string, idProject: stri
return response.data
};
export const apiGetProjectTask = async ({ user, id }: { user: string, id: string }) => {
const response = await api.get(`mobile/project/detail/${id}?user=${user}`);
export const apiGetProjectTask = async ({ user, id, cat }: { user: string, id: string, cat?: string }) => {
const response = await api.get(`mobile/project/detail/${id}?user=${user}${cat ? `&cat=${cat}` : ""}`);
return response.data;
};
export const apiEditProjectTask = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
export const apiEditProjectTask = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, dataDetail: any[] }, id: string }) => {
const response = await api.post(`/mobile/project/detail/${id}`, data)
return response.data;
};
@@ -522,12 +529,12 @@ export const apiUpdateStatusTaskDivision = async (data: { user: string, status:
return response.data
};
export const apiGetTaskTugas = async ({ user, id }: { user: string, id: string }) => {
const response = await api.get(`mobile/task/detail/${id}?user=${user}`);
export const apiGetTaskTugas = async ({ user, id, cat }: { user: string, id: string, cat?: string }) => {
const response = await api.get(`mobile/task/detail/${id}?user=${user}${cat ? `&cat=${cat}` : ""}`);
return response.data;
};
export const apiEditTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
export const apiEditTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, dataDetail: any[] }, id: string }) => {
const response = await api.post(`/mobile/task/detail/${id}`, data)
return response.data;
};
@@ -552,7 +559,7 @@ export const apiDeleteTaskMember = async (data: { user: string, idUser: string }
return response.data
};
export const apiCreateTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, idDivision: string }, id: string }) => {
export const apiCreateTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, idDivision: string, dataDetail: any[] }, id: string }) => {
const response = await api.post(`/mobile/task/${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

@@ -1,8 +1,9 @@
import { getApp } from '@react-native-firebase/app';
import { getDatabase } from '@react-native-firebase/database';
import Constants from 'expo-constants';
// Ganti URL sesuai punya kamu
const DATABASE_URL = 'https://mobile-darmasaba-default-rtdb.asia-southeast1.firebasedatabase.app';
const DATABASE_URL = Constants?.expoConfig?.extra?.URL_FIREBASE_DB
export function getDB() {
return getDatabase(getApp(), DATABASE_URL);

18
lib/fun_formatDateOnly.ts Normal file
View File

@@ -0,0 +1,18 @@
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import { DateType } from "react-native-ui-datepicker";
dayjs.extend(utc);
dayjs.extend(timezone);
export function formatDateOnly(
date?: DateType,
format: "DD-MM-YYYY" | "YYYY-MM-DD" = "DD-MM-YYYY"
) {
if (!date) return "";
return dayjs(date)
.tz(dayjs.tz.guess())
.format(format);
}

View File

@@ -0,0 +1,14 @@
import dayjs from "dayjs";
export function getDatesInRange(startDate: string, endDate: string) {
const dates = [];
const currentDate = new Date(startDate);
const endDateNew = new Date(endDate);
while (currentDate <= endDateNew) {
dates.push(dayjs(new Date(currentDate)).format("DD-MM-YYYY"));
currentDate.setDate(currentDate.getDate() + 1);
}
return dates;
}

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",

View File

@@ -1,3 +1,4 @@
import { ConstEnv } from '@/constants/ConstEnv';
import { apiRegisteredToken, apiUnregisteredToken } from '@/lib/api';
import { getToken, requestPermission } from '@/lib/useNotification';
import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -41,13 +42,13 @@ export default function AuthProvider({ children }: { children: ReactNode }): Rea
const decryptToken = (async (token: string) => {
var C = require("crypto-js");
var Decrypted = C.AES.decrypt(token, "your password");
var Decrypted = C.AES.decrypt(token, ConstEnv.pass_encrypt);
var result = Decrypted.toString(C.enc.Utf8);
return result
})
const encryptToken = (async (token: string) => {
var result = CryptoES.AES.encrypt(token, "your password").toString();
var result = CryptoES.AES.encrypt(token, ConstEnv.pass_encrypt).toString();
return result
})