Compare commits
14 Commits
amalia/14-
...
amalia/22-
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc69b64d2 | |||
| a409bfef2f | |||
| 99c81f6f0d | |||
| 4e6b27bbcc | |||
| 6a97ae76fc | |||
| 40c1fa4ed2 | |||
| e2c8f1db39 | |||
| 72fa18565d | |||
| b0e959e3e1 | |||
| 263875ae55 | |||
| 7810eb1686 | |||
| 0956dea846 | |||
| 2e5698b566 | |||
| 1ee9bea65e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,6 +32,9 @@ yarn-error.*
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
#env
|
||||
.env
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
|
||||
73
app.config.js
Normal file
73
app.config.js
Normal 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
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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]}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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) }}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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) }}
|
||||
/>
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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]}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
128
app/(application)/project/[id]/report.tsx
Normal file
128
app/(application)/project/[id]/report.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import ButtonBackHeader from "@/components/buttonBackHeader";
|
||||
import ButtonSaveHeader from "@/components/buttonSaveHeader";
|
||||
import { InputForm } from "@/components/inputForm";
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetProjectOne, apiReportProject } from "@/lib/api";
|
||||
import { setUpdateProject } from "@/lib/projectUpdate";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SafeAreaView, ScrollView, View } from "react-native";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
export default function ReportProject() {
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const dispatch = useDispatch()
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const [laporan, setLaporan] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
const [disable, setDisable] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetProjectOne({
|
||||
user: hasil,
|
||||
cat: "data",
|
||||
id: id,
|
||||
});
|
||||
setLaporan(response.data.report);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad();
|
||||
}, []);
|
||||
|
||||
function onValidation(val: string) {
|
||||
setLaporan(val)
|
||||
if (val == "" || val == "null") {
|
||||
setError(true)
|
||||
} else {
|
||||
setError(false)
|
||||
}
|
||||
}
|
||||
|
||||
function checkAll() {
|
||||
if (laporan == "" || laporan == "null" || laporan == null || laporan == undefined || error) {
|
||||
setDisable(true)
|
||||
} else {
|
||||
setDisable(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkAll()
|
||||
}, [laporan, error]);
|
||||
|
||||
async function handleUpdate() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiReportProject({
|
||||
report: laporan,
|
||||
user: hasil,
|
||||
}, id);
|
||||
if (response.success) {
|
||||
dispatch(setUpdateProject({ ...update, report: !update.report }))
|
||||
Toast.show({ type: 'small', text1: 'Berhasil mengubah data', })
|
||||
router.back();
|
||||
} else {
|
||||
Toast.show({ type: 'small', text1: response.message, })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerLeft: () => (
|
||||
<ButtonBackHeader
|
||||
onPress={() => {
|
||||
router.back();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
headerTitle: "Laporan Kegiatan",
|
||||
headerTitleAlign: "center",
|
||||
headerRight: () => (
|
||||
<ButtonSaveHeader
|
||||
disable={disable || loading}
|
||||
category="update"
|
||||
onPress={() => { handleUpdate() }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View style={[Styles.p15, Styles.mb100]}>
|
||||
<InputForm
|
||||
label="Laporan Kegiatan"
|
||||
type="default"
|
||||
placeholder="Laporan Kegiatan"
|
||||
required
|
||||
bg="white"
|
||||
value={laporan}
|
||||
onChange={(val) => { onValidation(val) }}
|
||||
error={error}
|
||||
errorText="Judul Kegiatan harus diisi"
|
||||
multiline
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
:
|
||||
|
||||
@@ -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
|
||||
?
|
||||
|
||||
135
components/project/modalAddDetailTugasProject.tsx
Normal file
135
components/project/modalAddDetailTugasProject.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
116
components/project/modalListDetailTugasProject.tsx
Normal file
116
components/project/modalListDetailTugasProject.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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") ? <></>
|
||||
:
|
||||
|
||||
@@ -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
|
||||
|
||||
55
components/project/sectionReportProject.tsx
Normal file
55
components/project/sectionReportProject.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetProjectOne } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
import Text from "../Text";
|
||||
import TextExpandable from "../textExpandable";
|
||||
|
||||
export default function SectionReportProject({ refreshing }: { refreshing?: boolean }) {
|
||||
const update = useSelector((state: any) => state.projectUpdate)
|
||||
const { token, decryptToken } = useAuthSession();
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const [data, setData] = useState("");
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current));
|
||||
const response = await apiGetProjectOne({
|
||||
user: hasil,
|
||||
cat: "data",
|
||||
id: id,
|
||||
});
|
||||
setData(response.data.report);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad();
|
||||
}, [update.report]);
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing)
|
||||
handleLoad();
|
||||
}, [refreshing]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
data != "" && data != null &&
|
||||
<View style={[Styles.mb15, Styles.mt10]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>
|
||||
Laporan Kegiatan
|
||||
</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
<TextExpandable content={data} maxLines={2} />
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
135
components/task/modalAddDetailTugasTask.tsx
Normal file
135
components/task/modalAddDetailTugasTask.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
116
components/task/modalListDetailTugasTask.tsx
Normal file
116
components/task/modalListDetailTugasTask.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
53
components/task/sectionReportTask.tsx
Normal file
53
components/task/sectionReportTask.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { apiGetTaskOne } from "@/lib/api";
|
||||
import { useAuthSession } from "@/providers/AuthProvider";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
import Text from "../Text";
|
||||
import TextExpandable from "../textExpandable";
|
||||
|
||||
|
||||
export default function SectionReportTask({ refreshing }: { refreshing: boolean }) {
|
||||
const update = useSelector((state: any) => state.taskUpdate)
|
||||
const { token, decryptToken } = useAuthSession()
|
||||
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
|
||||
const [data, setData] = useState('')
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
const hasil = await decryptToken(String(token?.current))
|
||||
const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'data' })
|
||||
setData(response.data.report)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleLoad()
|
||||
}, [update.report])
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing)
|
||||
handleLoad();
|
||||
}, [refreshing]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
data != "" && data != null &&
|
||||
<View style={[Styles.mb15, Styles.mt10]}>
|
||||
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>
|
||||
Laporan Kegiatan
|
||||
</Text>
|
||||
<View style={[Styles.wrapPaper]}>
|
||||
<TextExpandable content={data} maxLines={2} />
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
83
components/textExpandable.tsx
Normal file
83
components/textExpandable.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import Text from "./Text";
|
||||
|
||||
export default function TextExpandable({ content, maxLines }: { content: string, maxLines: number }) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [shouldShowMore, setShouldShowMore] = useState(false);
|
||||
const [collapsedHeight, setCollapsedHeight] = useState(0);
|
||||
const [fullHeight, setFullHeight] = useState(0);
|
||||
const animatedHeight = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const measureCollapsed = (e: any) => {
|
||||
if (collapsedHeight === 0) {
|
||||
setCollapsedHeight(e.nativeEvent.layout.height);
|
||||
animatedHeight.setValue(e.nativeEvent.layout.height);
|
||||
}
|
||||
};
|
||||
|
||||
const measureFull = (e: any) => {
|
||||
if (fullHeight === 0) {
|
||||
setFullHeight(e.nativeEvent.layout.height);
|
||||
}
|
||||
};
|
||||
|
||||
// Cek apakah memang perlu "View More"
|
||||
useEffect(() => {
|
||||
if (collapsedHeight > 0 && fullHeight > 0) {
|
||||
setShouldShowMore(fullHeight > collapsedHeight + 1); // +1 untuk toleransi float
|
||||
}
|
||||
}, [collapsedHeight, fullHeight]);
|
||||
|
||||
const toggleExpand = () => {
|
||||
Animated.timing(animatedHeight, {
|
||||
toValue: isExpanded ? collapsedHeight : fullHeight,
|
||||
duration: 300,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
{/* Hidden full text for measurement */}
|
||||
<View style={Styles.hidden}>
|
||||
<Text style={Styles.textDefault} onLayout={measureFull}>
|
||||
{content}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Collapsed text for measurement */}
|
||||
<View style={Styles.hidden}>
|
||||
<Text
|
||||
numberOfLines={maxLines}
|
||||
style={Styles.textDefault}
|
||||
onLayout={measureCollapsed}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Animated visible text */}
|
||||
<Animated.View style={{ height: animatedHeight, overflow: 'hidden' }}>
|
||||
<Text
|
||||
style={Styles.textDefault}
|
||||
numberOfLines={isExpanded ? undefined : maxLines}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
{shouldShowMore && (
|
||||
<Pressable onPress={toggleExpand}>
|
||||
<Text style={Styles.textLink}>
|
||||
{isExpanded ? 'View Less' : 'View More'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -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
6
constants/ConstEnv.ts
Normal 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
|
||||
}
|
||||
@@ -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
32
lib/api.ts
32
lib/api.ts
@@ -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
|
||||
|
||||
@@ -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
18
lib/fun_formatDateOnly.ts
Normal 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);
|
||||
}
|
||||
14
lib/fun_getDatesInRange.ts
Normal file
14
lib/fun_getDatesInRange.ts
Normal 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;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ const projectUpdate = createSlice({
|
||||
file: false,
|
||||
member: false,
|
||||
link: false,
|
||||
report: false,
|
||||
},
|
||||
reducers: {
|
||||
setUpdateProject: (state, action) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ const taskUpdate = createSlice({
|
||||
file: false,
|
||||
member: false,
|
||||
link: false,
|
||||
report: false,
|
||||
},
|
||||
reducers: {
|
||||
setUpdateTask: (state, action) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user