Merge pull request 'amalia/07-okt-25' (#47) from amalia/07-okt-25 into join

Reviewed-on: bip/mobile-darmasaba#47
This commit is contained in:
2025-10-07 17:42:13 +08:00
14 changed files with 90 additions and 38 deletions

View File

@@ -9,6 +9,7 @@ import SkeletonContent from "@/components/skeletonContent";
import Text from '@/components/Text'; import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import { regexOnlySpacesOrEnter } from "@/constants/OnlySpaceOrEnter";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api"; import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api";
import { getDB } from "@/lib/firebaseDatabase"; import { getDB } from "@/lib/firebaseDatabase";
@@ -221,14 +222,14 @@ export default function DetailDiscussionGeneral() {
multiline multiline
itemRight={ itemRight={
<Pressable onPress={() => { <Pressable onPress={() => {
(komentar != '' && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin"))) (komentar != '' && !regexOnlySpacesOrEnter.test(komentar) && data?.status === 1 && data?.isActive && (memberDiscussion || (entityUser.role != "user" && entityUser.role != "coadmin")))
&& handleKomentar() && handleKomentar()
}} }}
style={[ style={[
Platform.OS == 'android' && Styles.mb12, Platform.OS == 'android' && Styles.mb12,
]} ]}
> >
<MaterialIcons name="send" size={25} style={(komentar == '' || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} /> <MaterialIcons name="send" size={25} style={(komentar == '' || regexOnlySpacesOrEnter.test(komentar) || data?.status === 2 || !data?.isActive || (!memberDiscussion && (entityUser.role == "user" || entityUser.role == "coadmin"))) ? Styles.cGray : Styles.cDefault} />
</Pressable> </Pressable>
} }
/> />

View File

@@ -8,6 +8,7 @@ import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent"; import SkeletonContent from "@/components/skeletonContent";
import Text from "@/components/Text"; import Text from "@/components/Text";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import { regexOnlySpacesOrEnter } from "@/constants/OnlySpaceOrEnter";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { import {
apiGetDiscussionOne, apiGetDiscussionOne,
@@ -312,6 +313,7 @@ export default function DiscussionDetail() {
<Pressable <Pressable
onPress={() => { onPress={() => {
komentar != "" && komentar != "" &&
!regexOnlySpacesOrEnter.test(komentar) &&
!loadingSend && !loadingSend &&
data?.status != 2 && data?.status != 2 &&
data?.isActive && data?.isActive &&
@@ -333,6 +335,7 @@ export default function DiscussionDetail() {
size={25} size={25}
style={ style={
[komentar == "" || [komentar == "" ||
regexOnlySpacesOrEnter.test(komentar) ||
loadingSend || loadingSend ||
data?.status == 2 || data?.status == 2 ||
data?.isActive == false || data?.isActive == false ||

View File

@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@@ -94,7 +94,7 @@ export default function AddMemberTask() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -111,7 +111,7 @@ export default function AddMemberTask() {
) )
}} }}
/> />
<View style={[Styles.p15, Styles.mb100]}> <View style={[Styles.p15, { flex: 1 }]}>
<InputSearch onChange={(val) => setSearch(val)} value={search} /> <InputSearch onChange={(val) => setSearch(val)} value={search} />
{ {
@@ -172,6 +172,6 @@ export default function AddMemberTask() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@@ -64,7 +64,7 @@ export default function AddMemberCreateTask() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -81,7 +81,7 @@ export default function AddMemberCreateTask() {
) )
}} }}
/> />
<View style={[Styles.p15]}> <View style={[Styles.p15, { flex: 1 }]}>
<InputSearch onChange={(val) => setSearch(val)} value={search} /> <InputSearch onChange={(val) => setSearch(val)} value={search} />
{ {
@@ -138,6 +138,6 @@ export default function AddMemberCreateTask() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -99,14 +99,18 @@ export default function CreateTaskAddTugas() {
timeStart: item.timeStart, timeStart: item.timeStart,
timeEnd: item.timeEnd, timeEnd: item.timeEnd,
})) }))
dispatch(setTaskCreate([...taskCreate, { const hasilOrder = [...taskCreate, {
title: title, title: title,
dateStart: from, dateStart: from,
dateEnd: to, dateEnd: to,
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"), dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"), dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
dataDetail: dataDetailFix dataDetail: dataDetailFix
}])) }].sort((a, b) => {
return new Date(a.dateStartFix).getTime() - new Date(b.dateStartFix).getTime();
});
dispatch(setTaskCreate(hasilOrder))
router.back(); router.back();
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@@ -97,7 +97,7 @@ export default function AddMemberDivision() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -114,7 +114,7 @@ export default function AddMemberDivision() {
) )
}} }}
/> />
<View style={[Styles.p15]}> <View style={[Styles.p15, { flex: 1 }]}>
<InputSearch onChange={(val) => handleSearch(val)} value={search} /> <InputSearch onChange={(val) => handleSearch(val)} value={search} />
{ {
@@ -175,6 +175,6 @@ export default function AddMemberDivision() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -1,22 +1,28 @@
import AlertKonfirmasi from "@/components/alertKonfirmasi";
import ButtonBackHeader from "@/components/buttonBackHeader"; import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonNextHeader from "@/components/buttonNextHeader"; import ButtonNextHeader from "@/components/buttonNextHeader";
import { InputForm } from "@/components/inputForm"; import { InputForm } from "@/components/inputForm";
import ModalSelect from "@/components/modalSelect"; import ModalSelect from "@/components/modalSelect";
import SelectForm from "@/components/selectForm"; import SelectForm from "@/components/selectForm";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiCheckDivisionName } from "@/lib/api";
import { setFormCreateDivision } from "@/lib/divisionCreate"; import { setFormCreateDivision } from "@/lib/divisionCreate";
import { useAuthSession } from "@/providers/AuthProvider";
import { router, Stack } from "expo-router"; import { router, Stack } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { SafeAreaView, ScrollView, View } from "react-native"; import { SafeAreaView, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
export default function CreateDivision() { export default function CreateDivision() {
const [isSelect, setSelect] = useState(false); const { token, decryptToken } = useAuthSession()
const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" }); const [isSelect, setSelect] = useState(false)
const dispatch = useDispatch(); const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" })
const update = useSelector((state: any) => state.divisionCreate); const dispatch = useDispatch()
const update = useSelector((state: any) => state.divisionCreate)
const entityUser = useSelector((state: any) => state.user) const entityUser = useSelector((state: any) => state.user)
const userLogin = useSelector((state: any) => state.entities) const userLogin = useSelector((state: any) => state.entities)
const [loadingBtn, setLoadingBtn] = useState(false)
const [error, setError] = useState({ const [error, setError] = useState({
idGroup: false, idGroup: false,
name: false, name: false,
@@ -54,7 +60,35 @@ export default function CreateDivision() {
} }
} }
function handleSetData() { async function handleCheckName() {
try {
setLoadingBtn(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiCheckDivisionName({ data: { ...dataForm }, user: hasil })
if (response.success) {
if (!response.available) {
AlertKonfirmasi({
title: 'Peringatan',
desc: 'Nama divisi sudah ada. Apakah anda yakin ingin membuat divisi dengan nama yang sama?',
onPress: () => {
handleSetData()
}
})
} else {
handleSetData()
}
} else {
Toast.show({ type: 'small', text1: response.message, })
}
} catch (error) {
console.error(error)
Toast.show({ type: 'small', text1: 'Terjadi kesalahan', })
} finally {
setLoadingBtn(false)
}
}
async function handleSetData() {
dispatch(setFormCreateDivision({ ...update, data: dataForm })) dispatch(setFormCreateDivision({ ...update, data: dataForm }))
router.push(`./create/add-member`) router.push(`./create/add-member`)
} }
@@ -80,8 +114,8 @@ export default function CreateDivision() {
headerTitleAlign: "center", headerTitleAlign: "center",
headerRight: () => ( headerRight: () => (
<ButtonNextHeader <ButtonNextHeader
onPress={() => { handleSetData() }} onPress={() => { handleCheckName() }}
disable={error.idGroup || error.name || chooseGroup.val == "" || chooseGroup.val == "null" || dataForm.name == "" || dataForm.name == "null"} disable={loadingBtn || error.idGroup || error.name || chooseGroup.val == "" || chooseGroup.val == "null" || dataForm.name == "" || dataForm.name == "null"}
/> />
), ),
}} }}

View File

@@ -74,7 +74,7 @@ export default function CreateDivisionAddAdmin() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -91,7 +91,7 @@ export default function CreateDivisionAddAdmin() {
) )
}} }}
/> />
<View style={[Styles.p15]}> <View style={[Styles.p15, { flex: 1 }]}>
<ScrollView> <ScrollView>
{ {
data.length > 0 ? data.length > 0 ?
@@ -126,6 +126,6 @@ export default function CreateDivisionAddAdmin() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, ScrollView, View } from "react-native";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
type Props = { type Props = {
@@ -60,7 +60,7 @@ export default function CreateDivisionAddMember() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -74,7 +74,7 @@ export default function CreateDivisionAddMember() {
) )
}} }}
/> />
<View style={[Styles.p15]}> <View style={[Styles.p15, { flex: 1 }]}>
<InputSearch onChange={(val) => setSearch(val)} value={search} /> <InputSearch onChange={(val) => setSearch(val)} value={search} />
{ {
@@ -135,6 +135,6 @@ export default function CreateDivisionAddMember() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -12,7 +12,7 @@ import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@@ -94,7 +94,7 @@ export default function AddMemberProject() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -111,7 +111,7 @@ export default function AddMemberProject() {
) )
}} }}
/> />
<View style={[Styles.p15, Styles.mb100]}> <View style={[Styles.p15, { flex: 1 }]}>
<InputSearch onChange={(val) => handleSearch(val)} value={search} /> <InputSearch onChange={(val) => handleSearch(val)} value={search} />
{ {
selectMember.length > 0 selectMember.length > 0
@@ -176,6 +176,6 @@ export default function AddMemberProject() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -71,7 +71,7 @@ export default function AddMemberCreateProject() {
return ( return (
<SafeAreaView> <>
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
@@ -88,7 +88,7 @@ export default function AddMemberCreateProject() {
) )
}} }}
/> />
<View style={[Styles.p15]}> <View style={[Styles.p15, { flex: 1 }]}>
<InputSearch onChange={(val) => setSearch(val)} value={search} /> <InputSearch onChange={(val) => setSearch(val)} value={search} />
{ {
@@ -145,6 +145,6 @@ export default function AddMemberCreateProject() {
} }
</ScrollView> </ScrollView>
</View> </View>
</SafeAreaView> </>
) )
} }

View File

@@ -99,14 +99,19 @@ export default function CreateProjectAddTask() {
timeStart: item.timeStart, timeStart: item.timeStart,
timeEnd: item.timeEnd, timeEnd: item.timeEnd,
})) }))
dispatch(setTaskCreate([...taskCreate, {
const hasilOrder = [...taskCreate, {
title: title, title: title,
dateStart: from, dateStart: from,
dateEnd: to, dateEnd: to,
dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"), dateStartFix: formatDateOnly(range.startDate, "YYYY-MM-DD"),
dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"), dateEndFix: formatDateOnly(range.endDate, "YYYY-MM-DD"),
dataDetail: dataDetailFix dataDetail: dataDetailFix
}])) }].sort((a, b) => {
return new Date(a.dateStartFix).getTime() - new Date(b.dateStartFix).getTime();
});
dispatch(setTaskCreate(hasilOrder))
router.back(); router.back();
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@@ -0,0 +1 @@
export const regexOnlySpacesOrEnter = /^[\s\r\n]+$/;

View File

@@ -425,6 +425,10 @@ export const apiCreateDivision = async (data: { data: { idGroup: string, name: s
return response.data; return response.data;
}; };
export const apiCheckDivisionName = async (data: { data: { idGroup: string, name: string, desc: string }, user: string }) => {
const response = await api.put(`/mobile/division`, data)
return response.data;
};
export const apiGetDiscussion = async ({ user, search, division, active, page }: { user: string, search: string, division: string, active?: string, page?: number }) => { export const apiGetDiscussion = async ({ user, search, division, active, page }: { user: string, search: string, division: string, active?: string, page?: number }) => {
const response = await api.get(`mobile/discussion?user=${user}&active=${active}&search=${search}&division=${division}&page=${page}`); const response = await api.get(`mobile/discussion?user=${user}&active=${active}&search=${search}&division=${division}&page=${page}`);