upd: task division

>
> Deskripsi:
> - list task divisi
> - pencarian task divisi
> - detail task divisi
> - update status tugas task divisi
> - detail tugas task divisi
> - edit tugas task divisi
> - hapus tugas task divisi
> - hapus file task divisi
> - hapus member
> - tambah tugas task divisi
> - check file task divisi
> - tambah file task divisi
> - edit task divisi
> - cancel task divisi
> - tambah member task divisi
>
> No Issues
This commit is contained in:
amel
2025-05-26 17:40:10 +08:00
parent 84f4aab36e
commit bc74be122d
16 changed files with 1507 additions and 364 deletions

View File

@@ -2,60 +2,184 @@ import BorderBottomItem from "@/components/borderBottomItem";
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import ButtonSelect from "@/components/buttonSelect";
import DrawerBottom from "@/components/drawerBottom";
import MenuItemRow from "@/components/menuItemRow";
import Styles from "@/constants/Styles";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { apiAddFileTask, apiCheckFileTask } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
import * as DocumentPicker from "expo-document-picker";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native";
import { useState } from "react";
import {
ActivityIndicator,
SafeAreaView,
ScrollView,
Text,
ToastAndroid,
View,
} from "react-native";
import { useDispatch, useSelector } from "react-redux";
export default function TaskDivisionAddFile() {
const { id, detail } = useLocalSearchParams()
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
const [fileForm, setFileForm] = useState<any[]>([]);
const [listFile, setListFile] = useState<any[]>([]);
const [indexDelFile, setIndexDelFile] = useState<number>(0);
const [isModal, setModal] = useState(false);
const { token, decryptToken } = useAuthSession();
const [loadingCheck, setLoadingCheck] = useState(false);
const dispatch = useDispatch();
const update = useSelector((state: any) => state.taskUpdate);
const pickDocumentAsync = async () => {
let result = await DocumentPicker.getDocumentAsync({
type: ["*/*"],
multiple: false,
});
if (!result.canceled) {
if (result.assets?.[0].uri) {
const check = await handleCheckFile(result.assets?.[0]);
if (check) {
setFileForm([...fileForm, result.assets?.[0]]);
setListFile([...listFile, result.assets?.[0].name]);
} else {
ToastAndroid.show("File sudah ada", ToastAndroid.SHORT);
}
}
}
};
function deleteFile(index: number) {
setListFile([...listFile.filter((val, i) => i !== index)]);
setFileForm([...fileForm.filter((val, i) => i !== index)]);
setModal(false);
}
async function handleCheckFile(val: any) {
try {
setLoadingCheck(true);
const hasil = await decryptToken(String(token?.current));
const fd = new FormData();
fd.append("file", {
uri: val.uri,
type: "application/octet-stream",
name: val.name,
} as any);
fd.append(
"data",
JSON.stringify({
user: hasil,
})
);
const response = await apiCheckFileTask({ data: fd, id: detail });
return response.success;
} catch (error) {
console.error(error);
return false;
} finally {
setLoadingCheck(false);
}
}
async function handleAddFile() {
try {
const hasil = await decryptToken(String(token?.current));
const fd = new FormData();
for (let i = 0; i < fileForm.length; i++) {
fd.append(`file${i}`, {
uri: fileForm[i].uri,
type: "application/octet-stream",
name: fileForm[i].name,
} as any);
}
fd.append(
"data",
JSON.stringify({
user: hasil,
})
);
const response = await apiAddFileTask({ data: fd, id: detail });
if (response.success) {
ToastAndroid.show("Berhasil menambahkan file", ToastAndroid.SHORT);
dispatch(setUpdateTask({ ...update, file: !update.file }));
router.back();
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Terjadi kesalahan", ToastAndroid.SHORT);
}
}
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Tambah File',
headerTitleAlign: 'center',
headerRight: () => <ButtonSaveHeader category="create" onPress={() => {
ToastAndroid.show('Berhasil menambah data', ToastAndroid.SHORT)
router.push('./')
}} />
headerLeft: () => (
<ButtonBackHeader
onPress={() => {
router.back();
}}
/>
),
headerTitle: "Tambah File",
headerTitleAlign: "center",
headerRight: () => (
<ButtonSaveHeader
category="create"
disable={fileForm.length == 0 ? true : false}
onPress={() => { handleAddFile() }}
/>
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<ButtonSelect value="Upload File" />
<ButtonSelect value="Upload File" onPress={pickDocumentAsync} />
{
listFile.length > 0 && (
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>File</Text>
<View style={[Styles.wrapPaper]}>
{
listFile.map((item, index) => (
<BorderBottomItem
key={index}
borderType="all"
icon={<MaterialCommunityIcons name="file-outline" size={25} color="black" />}
title="image_pertama.jpg"
title={item}
titleWeight="normal"
onPress={() => { setIndexDelFile(index); setModal(true) }}
/>
<BorderBottomItem
borderType="all"
icon={<MaterialCommunityIcons name="file-outline" size={25} color="black" />}
title="file_kedua.pdf"
titleWeight="normal"
/>
</View>
</View>
{/* <ButtonForm
text="SIMPAN"
onPress={() => {
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah anda yakin ingin menambahkan data?',
onPress: () => {
ToastAndroid.show('Berhasil menambahkan data', ToastAndroid.SHORT)
router.push('./')
))
}
</View>
</View>
)
}
{
loadingCheck && <ActivityIndicator size="small" />
}
})
}} /> */}
</View>
</ScrollView>
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Menu">
<View style={Styles.rowItemsCenter}>
<MenuItemRow
icon={<Ionicons name="trash" color="black" size={25} />}
title="Hapus"
onPress={() => { deleteFile(indexDelFile) }}
/>
</View>
</DrawerBottom>
</SafeAreaView>
)
);
}

View File

@@ -0,0 +1,168 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Styles from "@/constants/Styles";
import { apiAddMemberTask, apiGetDivisionMember, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
type Props = {
idUser: string,
name: string,
img: string
}
export default function AddMemberTask() {
const dispatch = useDispatch()
const update = useSelector((state: any) => state.projectUpdate)
const { token, decryptToken } = useAuthSession()
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>()
const [dataOld, setDataOld] = useState<Props[]>([])
const [data, setData] = useState<Props[]>([])
const [selectMember, setSelectMember] = useState<any[]>([])
const [search, setSearch] = useState('')
async function handleLoadOldMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetTaskOne({
user: hasil,
id: detail,
cat: 'member',
});
setDataOld(response.data)
} catch (error) {
console.error(error)
}
}
async function handleLoadMemberDivision() {
const hasil = await decryptToken(String(token?.current))
const responMemberDivision = await apiGetDivisionMember({ id: id, user: hasil, search: search })
setData(responMemberDivision.data)
}
useEffect(() => {
handleLoadOldMember()
}, []);
useEffect(() => {
handleLoadMemberDivision()
}, [search]);
function onChoose(val: string, label: string, img?: string) {
if (selectMember.some((i: any) => i.idUser == val)) {
setSelectMember(selectMember.filter((i: any) => i.idUser != val))
} else {
setSelectMember([...selectMember, { idUser: val, name: label, img }])
}
}
async function handleAddMember() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiAddMemberTask({ id: detail, data: { user: hasil, member: selectMember, idDivision: id } })
if (response.success) {
ToastAndroid.show('Berhasil menambahkan anggota', ToastAndroid.SHORT)
dispatch(setUpdateTask({ ...update, member: !update.member }))
router.back()
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT)
}
} catch (error) {
console.error(error)
ToastAndroid.show('Gagal menambahkan anggota', ToastAndroid.SHORT)
}
}
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Tambah Anggota Kegiatan',
headerTitleAlign: 'center',
headerRight: () => (
<ButtonSaveHeader
category="update"
disable={selectMember.length > 0 ? false : true}
onPress={() => {
handleAddMember()
}}
/>
)
}}
/>
<View style={[Styles.p15]}>
<InputSearch onChange={(val) => setSearch(val)} value={search} />
{
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
}
</ScrollView>
</View>
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
{
data.length > 0 ?
data.map((item: any, index: any) => {
const found = dataOld.some((i: any) => i.idUser == item.idUser)
return (
<Pressable
key={index}
style={[Styles.itemSelectModal]}
onPress={() => {
!found && onChoose(item.idUser, item.name, item.img)
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>{item.name}</Text>
{
found && <Text style={[Styles.textInformation, Styles.cGray]}>sudah menjadi anggota</Text>
}
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.idUser) && <AntDesign name="check" size={20} />
}
</Pressable>
)
}
)
:
<Text style={[Styles.textDefault, { textAlign: 'center' }]}>Tidak ada data</Text>
}
</ScrollView>
</View>
</SafeAreaView>
)
}

View File

@@ -2,34 +2,123 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Styles from "@/constants/Styles";
import { apiCreateTaskTugas } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import dayjs from "dayjs";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native";
import DateTimePicker, { DateType, getDefaultStyles } from "react-native-ui-datepicker";
import { useEffect, useState } from "react";
import {
SafeAreaView,
ScrollView,
Text,
ToastAndroid,
View,
} from "react-native";
import DateTimePicker, { DateType } from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
export default function TaskDivisionAddTask() {
const { id, detail } = useLocalSearchParams()
const [range, setRange] = useState<{ startDate: DateType; endDate: DateType; }>({ startDate: undefined, endDate: undefined });
const defaultStyles = getDefaultStyles()
const { token, decryptToken } = useAuthSession();
const dispatch = useDispatch();
const update = useSelector((state: any) => state.taskUpdate);
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
const [disable, setDisable] = useState(true);
const [range, setRange] = useState<{
startDate: DateType;
endDate: DateType;
}>({ startDate: undefined, endDate: undefined });
const [error, setError] = useState({
startDate: false,
endDate: false,
title: false,
});
const [title, setTitle] = useState("");
const from = range.startDate
? dayjs(range.startDate).format('MMM DD, YYYY')
: '';
const to = range.endDate ? dayjs(range.endDate).format('MMM DD, YYYY') : '';
? dayjs(range.startDate).format("DD-MM-YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("DD-MM-YYYY") : "";
function checkAll() {
if (
from == "" ||
to == "" ||
title == "" ||
title == "null" ||
error.startDate ||
error.endDate ||
error.title
) {
setDisable(true);
} else {
setDisable(false);
}
}
function onValidation(cat: string, val: string) {
if (cat == "title") {
setTitle(val);
if (val == "" || val == "null") {
setError((error) => ({ ...error, title: true }));
} else {
setError((error) => ({ ...error, title: false }));
}
}
}
useEffect(() => {
checkAll();
}, [from, to, title, error]);
async function handleCreate() {
try {
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"),
user: hasil,
idDivision: id,
},
id: detail,
});
if (response.success) {
dispatch(setUpdateTask({ ...update, task: !update.task, progress: !update.progress }));
ToastAndroid.show("Berhasil menambah data", ToastAndroid.SHORT);
router.back();
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Gagal menambah data", ToastAndroid.SHORT);
}
}
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Tambah Tugas',
headerTitleAlign: 'center',
headerRight: () => <ButtonSaveHeader category="create" onPress={() => {
ToastAndroid.show('Berhasil menambah data', ToastAndroid.SHORT)
router.push('./')
}} />
headerLeft: () => (
<ButtonBackHeader
onPress={() => {
router.back();
}}
/>
),
headerTitle: "Tambah Tugas",
headerTitleAlign: "center",
headerRight: () => (
<ButtonSaveHeader
category="create"
disable={disable}
onPress={() => {
handleCreate();
}}
/>
),
}}
/>
<ScrollView>
@@ -40,43 +129,51 @@ export default function TaskDivisionAddTask() {
startDate={range.startDate}
endDate={range.endDate}
onChange={(param) => setRange(param)}
// styles={defaultStyles}
styles={{
selected: Styles.selectedDate,
selected_label:Styles.cWhite,
selected_label: Styles.cWhite,
range_fill: Styles.selectRangeDate,
}}
/>
</View>
<View style={[Styles.rowSpaceBetween, Styles.mv10]}>
<View style={[{ width: '48%' }]}>
<Text style={[Styles.mb05]}>Tanggal Mulai <Text style={Styles.cError}>*</Text></Text>
<View style={[Styles.mv10]}>
<View style={[Styles.rowSpaceBetween]}>
<View style={[{ width: "48%" }]}>
<Text style={[Styles.mb05]}>
Tanggal Mulai <Text style={Styles.cError}>*</Text>
</Text>
<View style={[Styles.wrapPaper, Styles.p10]}>
<Text style={{ textAlign: 'center' }}>{from}</Text>
<Text style={{ textAlign: "center" }}>{from}</Text>
</View>
</View>
<View style={[{ width: '48%' }]}>
<Text style={[Styles.mb05]}>Tanggal Berakhir <Text style={Styles.cError}>*</Text></Text>
<View style={[{ width: "48%" }]}>
<Text style={[Styles.mb05]}>
Tanggal Berakhir <Text style={Styles.cError}>*</Text>
</Text>
<View style={[Styles.wrapPaper, Styles.p10]}>
<Text style={{ textAlign: 'center' }}>{to}</Text>
<Text style={{ textAlign: "center" }}>{to}</Text>
</View>
</View>
</View>
<InputForm label="Judul Tugas" type="default" placeholder="Judul Tugas" required bg="white" />
{/* <ButtonForm
text="SIMPAN"
onPress={() => {
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah anda yakin ingin menambahkan data?',
onPress: () => {
ToastAndroid.show('Berhasil menambahkan data', ToastAndroid.SHORT)
router.push('./')
{
(error.endDate || error.startDate) && <Text style={[Styles.textInformation, Styles.cError, Styles.mt05]}>Tanggal tidak boleh kosong</Text>
}
})
}} /> */}
</View>
<InputForm
label="Judul Tugas"
type="default"
placeholder="Judul Tugas"
required
bg="white"
value={title}
error={error.title}
errorText="Judul tidak boleh kosong"
onChange={(e) => {
onValidation("title", e)
}}
/>
</View>
</ScrollView>
</SafeAreaView>
)
);
}

View File

@@ -1,42 +1,106 @@
import ButtonBackHeader from "@/components/buttonBackHeader"
import ButtonSaveHeader from "@/components/buttonSaveHeader"
import { InputForm } from "@/components/inputForm"
import Styles from "@/constants/Styles"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { SafeAreaView, ScrollView, ToastAndroid, View } from "react-native"
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Styles from "@/constants/Styles";
import { apiCancelTask } 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, ToastAndroid, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
export default function TaskDivisionCancel() {
const { id, detail } = useLocalSearchParams()
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
const { token, decryptToken } = useAuthSession();
const dispatch = useDispatch();
const update = useSelector((state: any) => state.projectUpdate);
const [reason, setReason] = useState("");
const [error, setError] = useState(false);
const [disable, setDisable] = useState(false);
function onValidation(val: string) {
setReason(val);
if (val == "" || val == "null") {
setError(true);
} else {
setError(false);
}
}
function checkAll() {
if (reason == "" || reason == "null" || error) {
setDisable(true);
} else {
setDisable(false);
}
}
useEffect(() => {
checkAll();
}, [reason, error]);
async function handleCancel() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiCancelTask(
{
reason: reason,
user: hasil,
},
detail
);
if (response.success) {
dispatch(setUpdateTask({ ...update, data: !update.data }));
ToastAndroid.show("Berhasil membatalkan kegiatan", ToastAndroid.SHORT);
router.back();
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Gagal membatalkan kegiatan", ToastAndroid.SHORT);
}
}
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Pembatalan Tugas',
headerTitleAlign: 'center',
headerRight: () => <ButtonSaveHeader category="cancel" onPress={() => {
ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT)
router.push('./')
}} />
headerLeft: () => (
<ButtonBackHeader
onPress={() => {
router.back();
}}
/>
),
headerTitle: "Pembatalan Tugas",
headerTitleAlign: "center",
headerRight: () => (
<ButtonSaveHeader
disable={disable}
category="cancel"
onPress={() => {
handleCancel();
}}
/>
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<InputForm label="Alasan Pembatalan" type="default" placeholder="Alasan Pembatalan" required bg="white" />
{/* <ButtonForm
text="SIMPAN"
onPress={() => {
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah anda yakin ingin membatalkan tugas? Pembatalan bersifat permanen',
onPress: () => {
ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT)
router.push('./')
}
})
}} /> */}
<InputForm
label="Alasan Pembatalan"
type="default"
placeholder="Alasan Pembatalan"
required
bg="white"
error={error}
errorText="Alasan pembatalan harus diisi"
onChange={(val) => onValidation(val)}
/>
</View>
</ScrollView>
</SafeAreaView>
)
);
}

View File

@@ -2,41 +2,122 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Styles from "@/constants/Styles";
import { apiEditTask, apiGetTaskOne } 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, ToastAndroid, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
export default function TaskDivisionEdit() {
const { id, detail } = useLocalSearchParams()
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
const { token, decryptToken } = useAuthSession();
const [judul, setJudul] = useState("");
const [error, setError] = useState(false);
const [disable, setDisable] = useState(false);
const dispatch = useDispatch();
const update = useSelector((state: any) => state.taskUpdate);
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetTaskOne({
user: hasil,
cat: "data",
id: detail,
});
setJudul(response.data.title);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad();
}, []);
function onValidation(val: string) {
setJudul(val);
if (val == "" || val == "null") {
setError(true);
}else{
setError(false);
}
}
function checkAll() {
if (judul == "" || judul == "null" || error) {
setDisable(true);
} else {
setDisable(false);
}
}
useEffect(() => {
checkAll();
}, [judul, error]);
async function handleUpdate() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiEditTask(
{
title: judul,
user: hasil,
},
detail
);
if (response.success) {
dispatch(setUpdateTask({ ...update, data: !update.data }));
ToastAndroid.show("Berhasil mengubah data", ToastAndroid.SHORT);
router.back();
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Terjadi kesalahan", ToastAndroid.SHORT);
}
}
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Edit Judul Tugas',
headerTitleAlign: 'center',
headerRight: () => <ButtonSaveHeader category="update" onPress={() => {
ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT)
router.push('./')
}} />
headerLeft: () => (
<ButtonBackHeader
onPress={() => {
router.back();
}}
/>
),
headerTitle: "Edit Judul",
headerTitleAlign: "center",
headerRight: () => (
<ButtonSaveHeader
category="update"
disable={disable}
onPress={() => { handleUpdate() }}
/>
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<InputForm label="Judul Kegiatan" type="default" placeholder="Judul Kegiatan" required bg="white" />
{/* <ButtonForm
text="SIMPAN"
onPress={() => {
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah anda yakin ingin mengubah data?',
onPress: () => {
ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT)
router.push('./')
}
})
}} /> */}
<InputForm
label="Judul Kegiatan"
type="default"
placeholder="Judul Kegiatan"
required
bg="white"
value={judul}
onChange={(val) => { onValidation(val) }}
error={error}
errorText="Judul Kegiatan harus diisi"
/>
</View>
</ScrollView>
</SafeAreaView>
)
);
}

View File

@@ -1,15 +1,54 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import SectionCancel from "@/components/sectionCancel";
import SectionProgress from "@/components/sectionProgress";
import HeaderRightTaskDetail from "@/components/task/headerTaskDetail";
import SectionFileTask from "@/components/task/sectionFileTask";
import SectionMemberTask from "@/components/task/sectionMemberTask";
import SectionTanggalTugasTask from "@/components/task/sectionTanggalTugasTask";
import Styles from "@/constants/Styles";
import { apiGetTaskOne } from "@/lib/api";
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 { useSelector } from "react-redux";
type Props = {
id: string
title: string
desc: string
reason: string
status: number
isActive: boolean
}
export default function DetailTaskDivision() {
const { id, detail } = useLocalSearchParams()
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
const { token, decryptToken } = useAuthSession()
const [data, setData] = useState<Props>()
const [loading, setLoading] = useState(true)
const [progress, setProgress] = useState(0)
const update = useSelector((state: any) => state.taskUpdate)
async function handleLoad() {
try {
setLoading(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'data' })
setData(response.data)
const responseProgress = await apiGetTaskOne({ id: detail, user: hasil, cat: 'progress' })
setProgress(responseProgress.data.progress)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad()
}, [update.progress, update.data])
return (
@@ -17,14 +56,17 @@ export default function DetailTaskDivision() {
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Judul Tugas Divisi',
headerTitle: loading ? 'Loading...' : data?.title,
headerTitleAlign: 'center',
headerRight: () => <HeaderRightTaskDetail id={detail} />,
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<SectionProgress text="Kemajuan Kegiatan 50%" progress={50} />
{
data?.reason != null && data?.reason != "" && <SectionCancel text={data?.reason} />
}
<SectionProgress text={`Kemajuan Kegiatan ${progress}%`} progress={progress} />
<SectionTanggalTugasTask />
<SectionFileTask />
<SectionMemberTask />

View File

@@ -6,14 +6,51 @@ import PaperGridContent from "@/components/paperGridContent";
import ProgressBar from "@/components/progressBar";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { AntDesign, Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
import { apiGetTask } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import {
AntDesign,
Ionicons,
MaterialCommunityIcons,
} from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, Text, View } from "react-native";
type Props = {
id: string;
title: string;
desc: string;
status: number;
progress: number;
member: number;
};
export default function ListTask() {
const { status } = useLocalSearchParams<{ status: string }>()
const [isList, setList] = useState(false)
const { id, status } = useLocalSearchParams<{ id: string; status: string }>();
const [isList, setList] = useState(false);
const { token, decryptToken } = useAuthSession();
const [data, setData] = useState<Props[]>([]);
const [search, setSearch] = useState("");
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetTask({
user: hasil,
division: id,
status,
search,
});
setData(response.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad();
}, [status, search]);
return (
<SafeAreaView>
@@ -23,112 +60,152 @@ export default function ListTask() {
<ButtonTab
active={status}
value="0"
onPress={() => { router.push('./task?status=0') }}
onPress={() => {
router.replace("./task?status=0");
}}
label="Segera"
icon={<MaterialCommunityIcons name="clock-alert-outline" color={status == "0" ? 'white' : 'black'} size={20} />}
n={4} />
icon={
<MaterialCommunityIcons
name="clock-alert-outline"
color={status == "0" ? "white" : "black"}
size={20}
/>
}
n={4}
/>
<ButtonTab
active={status}
value="1"
onPress={() => { router.push('./task?status=1') }}
onPress={() => {
router.replace("./task?status=1");
}}
label="Dikerjakan"
icon={<MaterialCommunityIcons name="progress-check" color={status == "1" ? 'white' : 'black'} size={20} />}
n={4} />
icon={
<MaterialCommunityIcons
name="progress-check"
color={status == "1" ? "white" : "black"}
size={20}
/>
}
n={4}
/>
<ButtonTab
active={status}
value="2"
onPress={() => { router.push('./task?status=2') }}
onPress={() => {
router.replace("./task?status=2");
}}
label="Selesai"
icon={<Ionicons name="checkmark-done-circle-outline" color={status == "2" ? 'white' : 'black'} size={20} />}
n={4} />
icon={
<Ionicons
name="checkmark-done-circle-outline"
color={status == "2" ? "white" : "black"}
size={20}
/>
}
n={4}
/>
<ButtonTab
active={status}
value="3"
onPress={() => { router.push('./task?status=3') }}
onPress={() => {
router.replace("./task?status=3");
}}
label="Batal"
icon={<AntDesign name="closecircleo" color={status == "3" ? 'white' : 'black'} size={20} />}
n={4} />
icon={
<AntDesign
name="closecircleo"
color={status == "3" ? "white" : "black"}
size={20}
/>
}
n={4}
/>
</ScrollView>
<View style={[Styles.rowSpaceBetween]}>
<InputSearch width={68} />
<Pressable onPress={() => { setList(!isList) }}>
<MaterialCommunityIcons name={isList ? 'format-list-bulleted' : 'view-grid'} color={"black"} size={30} />
<InputSearch width={68} onChange={setSearch} />
<Pressable
onPress={() => {
setList(!isList);
}}
>
<MaterialCommunityIcons
name={isList ? "format-list-bulleted" : "view-grid"}
color={"black"}
size={30}
/>
</Pressable>
</View>
{
isList
?
{data.length > 0 ? (
<></>
) : (
<Text
style={[
Styles.textDefault,
Styles.cGray,
{ textAlign: "center" },
]}
>
Tidak ada data
</Text>
)}
{isList ? (
<View>
{data.map((item, index) => (
<BorderBottomItem
onPress={() => { }}
key={index}
onPress={() => {
router.push(`./task/${item.id}`);
}}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<AntDesign name="areachart" size={25} color={'#384288'} />
<AntDesign name="areachart" size={25} color={"#384288"} />
</View>
}
title="Pembangunan Jembatan"
title={item.title}
/>
<BorderBottomItem
onPress={() => { }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<AntDesign name="areachart" size={25} color={'#384288'} />
))}
</View>
}
title="Pembangunan Jembatan"
/>
<BorderBottomItem
onPress={() => { }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<AntDesign name="areachart" size={25} color={'#384288'} />
</View>
}
title="Pembangunan Jembatan"
/>
<BorderBottomItem
onPress={() => { }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<AntDesign name="areachart" size={25} color={'#384288'} />
</View>
}
title="Pembangunan Jembatan"
/>
</View>
:
) : (
<View>
<PaperGridContent onPress={() => { router.push('./task/321') }} content="page" title="Pembangunan Jembatan" headerColor="primary">
<ProgressBar category="page" value={0}/>
{data.map((item, index) => (
<PaperGridContent
key={index}
onPress={() => {
router.push(`./task/${item.id}`);
}}
content="page"
title={item.title}
headerColor="primary"
>
<ProgressBar category="page" value={item.progress} />
<View style={[Styles.rowSpaceBetween]}>
<Text style={[Styles.textDefault, Styles.cGray]}>13 Februari 2025</Text>
<LabelStatus size="default" category="primary" text="SEGERA" />
</View>
</PaperGridContent>
<PaperGridContent content="page" title="Pembangunan Jembatan" headerColor="primary">
<ProgressBar category="page" value={0}/>
<View style={[Styles.rowSpaceBetween]}>
<Text style={[Styles.textDefault, Styles.cGray]}>13 Februari 2025</Text>
<LabelStatus size="default" category="primary" text="SEGERA" />
</View>
</PaperGridContent>
<PaperGridContent content="page" title="Pembangunan Jembatan" headerColor="primary">
<ProgressBar category="page" value={0}/>
<View style={[Styles.rowSpaceBetween]}>
<Text style={[Styles.textDefault, Styles.cGray]}>13 Februari 2025</Text>
<LabelStatus size="default" category="primary" text="SEGERA" />
</View>
</PaperGridContent>
</View>
<Text></Text>
<LabelStatus
size="default"
category={
item.status === 0 ? 'primary' :
item.status === 1 ? 'warning' :
item.status === 2 ? 'success' :
item.status === 3 ? 'error' :
'primary'
}
text={
item.status === 0 ? 'SEGERA' :
item.status === 1 ? 'DIKERJAKAN' :
item.status === 2 ? 'SELESAI' :
item.status === 3 ? 'DIBATALKAN' :
'SEGERA'
}
/>
</View>
</PaperGridContent>
))}
</View>
)}
</View>
</ScrollView>
</SafeAreaView>
)
);
}

View File

@@ -0,0 +1,207 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Styles from "@/constants/Styles";
import { apiEditTaskTugas, apiGetTaskTugas } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import dayjs from "dayjs";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import {
SafeAreaView,
ScrollView,
Text,
ToastAndroid,
View,
} from "react-native";
import DateTimePicker, { DateType } from "react-native-ui-datepicker";
import { useDispatch, useSelector } from "react-redux";
export default function UpdateProjectTaskDivision() {
const { detail } = useLocalSearchParams<{ detail: string }>();
const dispatch = useDispatch();
const update = useSelector((state: any) => state.taskUpdate);
const { token, decryptToken } = useAuthSession();
const [range, setRange] = useState<{
startDate: DateType;
endDate: DateType;
}>({ startDate: undefined, endDate: undefined });
const [month, setMonth] = useState<any>();
const [year, setYear] = useState<any>();
const [loading, setLoading] = useState(true);
const [disableBtn, setDisableBtn] = useState(false);
const [title, setTitle] = useState("");
const [error, setError] = useState({
startDate: false,
endDate: false,
title: false,
});
const from = range.startDate
? dayjs(range.startDate).format("MMM DD, YYYY")
: "";
const to = range.endDate ? dayjs(range.endDate).format("MMM DD, YYYY") : "";
async function handleLoad() {
try {
setLoading(true);
const hasil = await decryptToken(String(token?.current));
const response = await apiGetTaskTugas({
user: hasil,
id: detail,
});
setTitle(response.data.title);
setRange({
startDate: new Date(response.data.dateStart),
endDate: new Date(response.data.dateEnd),
});
setMonth(new Date(response.data.dateStart).getMonth());
setYear(new Date(response.data.dateStart).getFullYear());
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
handleLoad();
}, []);
async function handleEdit() {
try {
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 });
if (response.success) {
dispatch(setUpdateTask({ ...update, task: !update.task }))
ToastAndroid.show("Berhasil mengubah data", ToastAndroid.SHORT);
router.back();
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Gagal mengubah data", ToastAndroid.SHORT);
}
}
function checkAll() {
if (
from == "" ||
to == "" ||
title == "" ||
title == "null" ||
error.startDate ||
error.endDate ||
error.title
) {
setDisableBtn(true);
} else {
setDisableBtn(false);
}
}
function onValidation(cat: string, val: string) {
if (cat == "title") {
setTitle(val);
if (val == "" || val == "null") {
setError((error) => ({ ...error, title: true }));
} else {
setError((error) => ({ ...error, title: false }));
}
}
}
useEffect(() => {
checkAll();
}, [from, to, title, error]);
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => (
<ButtonBackHeader
onPress={() => {
router.back();
}}
/>
),
headerTitle: "Edit Tanggal dan Tugas",
headerTitleAlign: "center",
headerRight: () => (
<ButtonSaveHeader
disable={disableBtn}
category="update"
onPress={() => {
handleEdit()
}}
/>
),
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<View style={[Styles.wrapPaper, Styles.p10]}>
{!loading && (
<DateTimePicker
mode="range"
startDate={range.startDate}
endDate={range.endDate}
onChange={(param) => setRange(param)}
month={month}
year={year}
styles={{
selected: Styles.selectedDate,
selected_label: Styles.cWhite,
range_fill: Styles.selectRangeDate,
}}
/>
)}
</View>
<View style={[Styles.mv10]}>
<View style={[Styles.rowSpaceBetween]}>
<View style={[{ width: "48%" }]}>
<Text style={[Styles.mb05]}>
Tanggal Mulai <Text style={Styles.cError}>*</Text>
</Text>
<View style={[Styles.wrapPaper, Styles.p10]}>
<Text style={{ textAlign: "center" }}>{from}</Text>
</View>
</View>
<View style={[{ width: "48%" }]}>
<Text style={[Styles.mb05]}>
Tanggal Berakhir <Text style={Styles.cError}>*</Text>
</Text>
<View style={[Styles.wrapPaper, Styles.p10]}>
<Text style={{ textAlign: "center" }}>{to}</Text>
</View>
</View>
</View>
{(error.endDate || error.startDate) && (
<Text
style={[Styles.textInformation, Styles.cError, Styles.mt05]}
>
Tanggal tidak boleh kosong
</Text>
)}
</View>
<InputForm
label="Judul Tugas"
type="default"
placeholder="Judul Tugas"
required
bg="white"
value={title}
error={error.title}
errorText="Judul tidak boleh kosong"
onChange={(e) => {
onValidation("title", e)
}}
/>
</View>
</ScrollView>
</SafeAreaView>
);
}

View File

@@ -1,70 +0,0 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import { InputForm } from "@/components/inputForm";
import Styles from "@/constants/Styles";
import dayjs from "dayjs";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native";
import DateTimePicker, { DateType, getDefaultStyles } from "react-native-ui-datepicker";
export default function UpdateProjectTaskDivision() {
const { id } = useLocalSearchParams()
const [range, setRange] = useState<{ startDate: DateType; endDate: DateType; }>({ startDate: undefined, endDate: undefined });
const defaultStyles = getDefaultStyles()
const from = range.startDate
? dayjs(range.startDate).format('MMM DD, YYYY')
: '';
const to = range.endDate ? dayjs(range.endDate).format('MMM DD, YYYY') : '';
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Edit Tanggal dan Tugas Divisi',
headerTitleAlign: 'center',
headerRight: () => <ButtonSaveHeader category="update" onPress={() => {
ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT)
router.push('../4324')
}} />
}}
/>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<View style={[Styles.wrapPaper, Styles.p10]}>
<DateTimePicker
mode="range"
startDate={range.startDate}
endDate={range.endDate}
onChange={(param) => setRange(param)}
// styles={defaultStyles}
styles={{
selected: Styles.selectedDate,
selected_label: Styles.cWhite,
range_fill: Styles.selectRangeDate,
}}
/>
</View>
<View style={[Styles.rowSpaceBetween, Styles.mv10]}>
<View style={[{ width: '48%' }]}>
<Text style={[Styles.mb05]}>Tanggal Mulai <Text style={Styles.cError}>*</Text></Text>
<View style={[Styles.wrapPaper, Styles.p10]}>
<Text style={{ textAlign: 'center' }}>{from}</Text>
</View>
</View>
<View style={[{ width: '48%' }]}>
<Text style={[Styles.mb05]}>Tanggal Berakhir <Text style={Styles.cError}>*</Text></Text>
<View style={[Styles.wrapPaper, Styles.p10]}>
<Text style={{ textAlign: 'center' }}>{to}</Text>
</View>
</View>
</View>
<InputForm label="Judul Tugas" type="default" placeholder="Judul Tugas" required bg="white" />
</View>
</ScrollView>
</SafeAreaView>
)
}

View File

@@ -1,11 +1,11 @@
import Styles from "@/constants/Styles"
import { AntDesign, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router } from "expo-router"
import { useState } from "react"
import { View } from "react-native"
import ButtonMenuHeader from "../buttonMenuHeader"
import DrawerBottom from "../drawerBottom"
import MenuItemRow from "../menuItemRow"
import { router } from "expo-router"
type Props = {
id: string | string[]
@@ -40,8 +40,8 @@ export default function HeaderRightTaskDetail({ id }: Props) {
icon={<MaterialIcons name="groups" color="black" size={25} />}
title="Tambah Anggota"
onPress={() => {
// setVisible(false)
setVisible(false)
router.push(`./${id}/add-member`)
}}
/>

View File

@@ -1,34 +1,87 @@
import Styles from "@/constants/Styles";
import { apiDeleteFileTask, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
import { useState } from "react";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Text, ToastAndroid, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
import AlertKonfirmasi from "../alertKonfirmasi";
import BorderBottomItem from "../borderBottomItem";
import DrawerBottom from "../drawerBottom";
import MenuItemRow from "../menuItemRow";
type Props = {
id: string
name: string
extension: string
idStorage: string
}
export default function SectionFileTask() {
const [isModal, setModal] = useState(false)
const { token, decryptToken } = useAuthSession()
const { detail } = useLocalSearchParams<{ detail: string }>();
const [data, setData] = useState<Props[]>([])
const [idSelect, setIdSelect] = useState('')
const update = useSelector((state: any) => state.taskUpdate)
const dispatch = useDispatch()
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'file' })
setData(response.data)
} catch (error) {
console.error(error)
}
}
useEffect(() => {
handleLoad()
}, [update.file])
async function handleDelete() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiDeleteFileTask({ user: hasil }, idSelect);
if (response.success) {
ToastAndroid.show('Berhasil menghapus file', ToastAndroid.SHORT)
dispatch(setUpdateTask({ ...update, file: !update.file }))
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT)
}
} catch (error) {
console.error(error);
ToastAndroid.show('Terjadi kesalahan', ToastAndroid.SHORT)
} finally {
setModal(false)
}
}
return (
<>
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>File</Text>
<View style={[Styles.wrapPaper]}>
{
data.length > 0 ?
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="all"
icon={<MaterialCommunityIcons name="file-outline" size={25} color="black" />}
title="image_pertama.jpg"
title={item.name + '.' + item.extension}
titleWeight="normal"
onPress={() => { setModal(true) }}
/>
<BorderBottomItem
borderType="all"
icon={<MaterialCommunityIcons name="file-outline" size={25} color="black" />}
title="file_kedua.pdf"
titleWeight="normal"
onPress={() => { setModal(true) }}
onPress={() => { setIdSelect(item.id); setModal(true) }}
/>
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada file</Text>
}
</View>
</View>
@@ -57,8 +110,7 @@ export default function SectionFileTask() {
title: 'Konfirmasi',
desc: 'Apakah Anda yakin ingin menghapus file ini? File yang dihapus tidak dapat dikembalikan',
onPress: () => {
setModal(false)
ToastAndroid.show('Berhasil menghapus data', ToastAndroid.SHORT)
handleDelete()
}
})

View File

@@ -1,67 +1,165 @@
import Styles from "@/constants/Styles"
import { MaterialCommunityIcons } from "@expo/vector-icons"
import { router } from "expo-router"
import { useState } from "react"
import { Image, Text, ToastAndroid, View } from "react-native"
import AlertKonfirmasi from "../alertKonfirmasi"
import BorderBottomItem from "../borderBottomItem"
import DrawerBottom from "../drawerBottom"
import MenuItemRow from "../menuItemRow"
import Styles from "@/constants/Styles";
import { apiDeleteTaskMember, apiGetTaskOne } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Text, ToastAndroid, View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
import AlertKonfirmasi from "../alertKonfirmasi";
import BorderBottomItem from "../borderBottomItem";
import DrawerBottom from "../drawerBottom";
import ImageUser from "../imageNew";
import MenuItemRow from "../menuItemRow";
type Props = {
id: string;
idUser: string;
name: string;
email: string;
img: string;
position: string;
};
export default function SectionMemberTask() {
const [isModal, setModal] = useState(false)
const [isModal, setModal] = useState(false);
const { token, decryptToken } = useAuthSession();
const { id, detail } = useLocalSearchParams<{ id: string; detail: string }>();
const [data, setData] = useState<Props[]>([]);
const dispatch = useDispatch();
const update = useSelector((state: any) => state.taskUpdate);
const [memberChoose, setMemberChoose] = useState({
id: "",
name: "",
});
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetTaskOne({
id: detail,
user: hasil,
cat: "member",
});
setData(response.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad();
}, [update.member]);
async function handleDeleteMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiDeleteTaskMember({
user: hasil,
idUser: memberChoose.id,
}, detail)
if (response.success) {
ToastAndroid.show("Berhasil menghapus anggota", ToastAndroid.SHORT);
dispatch(setUpdateTask({ ...update, member: !update.progress }))
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Gagal menghapus anggota", ToastAndroid.SHORT);
} finally {
setModal(false);
}
}
return (
<>
<View style={[Styles.mb15]}>
<View style={[Styles.rowSpaceBetween, Styles.mv05]}>
<Text style={[Styles.textDefaultSemiBold]}>Anggota</Text>
<Text style={[Styles.textDefault]}>Total 5 Anggota</Text>
<Text style={[Styles.textDefault]}>Total {data.length} Anggota</Text>
</View>
<View style={[Styles.wrapPaper]}>
{data.length > 0 ? (
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="bottom"
icon={
<></>
}
title="Amalia Dwi"
subtitle="Dinas - Bendahara"
rightTopInfo="Anggota"
onPress={() => { setModal(true) }}
<ImageUser
src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
/>
}
title={item.name}
onPress={() => {
setMemberChoose({
id: item.idUser,
name: item.name,
});
setModal(true);
}}
/>
);
})
) : (
<Text
style={[
Styles.textDefault,
Styles.cGray,
{ textAlign: "center" },
]}
>
Tidak ada anggota
</Text>
)}
</View>
</View>
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Menu">
<DrawerBottom
animation="slide"
isVisible={isModal}
setVisible={setModal}
title={memberChoose.name}
>
<View style={Styles.rowItemsCenter}>
<MenuItemRow
icon={<MaterialCommunityIcons name="account-eye" color="black" size={25} />}
icon={
<MaterialCommunityIcons
name="account-eye"
color="black"
size={25}
/>
}
title="Lihat Profil"
onPress={() => {
setModal(false)
router.push('/member/123')
setModal(false);
router.push(`/member/${memberChoose.id}`);
}}
/>
<MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color="black" size={25} />}
icon={
<MaterialCommunityIcons
name="account-remove"
color="black"
size={25}
/>
}
title="Keluarkan"
onPress={() => {
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah Anda yakin ingin mengeluarkan anggota?',
onPress: () => {
setModal(false)
ToastAndroid.show('Berhasil mengeluarkan anggota', ToastAndroid.SHORT)
}
})
title: "Konfirmasi",
desc: "Apakah Anda yakin ingin mengeluarkan anggota?",
onPress: () => { handleDeleteMember() },
});
}}
/>
</View>
</DrawerBottom>
</>
)
);
}

View File

@@ -1,27 +1,128 @@
import Styles from "@/constants/Styles";
import { apiDeleteTaskTugas, apiGetTaskOne, apiUpdateStatusTaskDivision } from "@/lib/api";
import { setUpdateTask } from "@/lib/taskUpdate";
import { useAuthSession } from "@/providers/AuthProvider";
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
import { router } from "expo-router";
import { useState } from "react";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Text, ToastAndroid, View } from "react-native";
import ItemSectionTanggalTugas from "../itemSectionTanggalTugas";
import DrawerBottom from "../drawerBottom";
import MenuItemRow from "../menuItemRow";
import { useDispatch, useSelector } from "react-redux";
import AlertKonfirmasi from "../alertKonfirmasi";
import DrawerBottom from "../drawerBottom";
import ItemSectionTanggalTugas from "../itemSectionTanggalTugas";
import MenuItemRow from "../menuItemRow";
import ModalSelect from "../modalSelect";
type Props = {
id: string;
title: string;
status: number;
dateStart: string;
dateEnd: string;
}
export default function SectionTanggalTugasTask() {
const dispatch = useDispatch()
const update = useSelector((state: any) => state.taskUpdate)
const [isModal, setModal] = useState(false)
const [isSelect, setSelect] = useState(false)
const [choose, setChoose] = useState({ val: '', label: '' })
const { token, decryptToken } = useAuthSession()
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
const [data, setData] = useState<Props[]>([])
const [tugas, setTugas] = useState({
id: '',
status: 0,
})
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetTaskOne({ id: detail, user: hasil, cat: 'task' })
setData(response.data)
} catch (error) {
console.error(error)
}
}
async function handleUpdate(status: number) {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiUpdateStatusTaskDivision({
user: hasil,
idProject: detail,
status: status,
}, tugas.id);
if (response.success) {
dispatch(setUpdateTask({ ...update, progress: !update.progress, task: !update.task }))
ToastAndroid.show("Berhasil mengubah data", ToastAndroid.SHORT);
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Gagal mengubah data", ToastAndroid.SHORT);
} finally {
setSelect(false)
}
}
useEffect(() => {
handleLoad()
}, [update.task])
async function handleDelete() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiDeleteTaskTugas({
user: hasil,
idProject: detail,
}, tugas.id);
if (response.success) {
dispatch(setUpdateTask({ ...update, progress: !update.progress, task: !update.task }))
ToastAndroid.show("Berhasil menghapus data", ToastAndroid.SHORT);
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show("Gagal menghapus data", ToastAndroid.SHORT);
} finally {
setModal(false);
}
}
return (
<>
<View style={[Styles.mb15, Styles.mt10]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>Tanggal & Tugas</Text>
<View style={[Styles.wrapPaper]}>
<ItemSectionTanggalTugas done={false} title="Pertama" dateStart="12-03-2023" dateEnd="15-03-2023" onPress={() => { setModal(true) }} />
<ItemSectionTanggalTugas done={true} title="Kedua" dateStart="15-03-2023" dateEnd="20-03-2023" onPress={() => { setModal(true) }} />
{
data.length > 0
?
data.map((item, index) => {
return (
<ItemSectionTanggalTugas
key={index}
done={item.status === 1}
title={item.title}
dateStart={item.dateStart}
dateEnd={item.dateEnd}
onPress={() => {
setTugas({
id: item.id,
status: item.status
})
setModal(true)
}}
/>
);
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada tugas</Text>
}
</View>
</View>
@@ -40,7 +141,7 @@ export default function SectionTanggalTugasTask() {
title="Edit Tugas"
onPress={() => {
setModal(false)
router.push(`./update/124`)
router.push(`./update/${tugas.id}`)
}}
/>
@@ -52,8 +153,7 @@ export default function SectionTanggalTugasTask() {
title: 'Konfirmasi',
desc: 'Apakah anda yakin ingin menghapus data ini?',
onPress: () => {
setModal(false)
ToastAndroid.show('Berhasil menghapus data', ToastAndroid.SHORT)
handleDelete()
}
})
@@ -66,12 +166,11 @@ export default function SectionTanggalTugasTask() {
category="status-task"
close={setSelect}
onSelect={(value) => {
setChoose(value)
setSelect(false)
ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT)
handleUpdate(Number(value.val))
}}
title="Status"
open={isSelect}
valChoose={String(tugas.status)}
/>
</>
)

View File

@@ -468,3 +468,85 @@ export const apiGetCalendarHistory = async ({ user, search, division }: { user:
const response = await api.get(`mobile/calendar/history?user=${user}&search=${search}&division=${division}`);
return response.data;
};
export const apiGetTask = async ({ user, status, search, division }: { user: string, status: string, search: string, division: string }) => {
const response = await api.get(`mobile/task?user=${user}&status=${status}&division=${division}&search=${search}`);
return response.data;
};
export const apiGetTaskOne = async ({ user, cat, id }: { user: string, cat: 'data' | 'progress' | 'task' | 'file' | 'member', id: string }) => {
const response = await api.get(`mobile/task/${id}?user=${user}&cat=${cat}`);
return response.data;
};
export const apiUpdateStatusTaskDivision = async (data: { user: string, status: number, idProject: string }, id: string) => {
const response = await api.put(`mobile/task/detail/${id}`, data)
return response.data
};
export const apiGetTaskTugas = async ({ user, id }: { user: string, id: string }) => {
const response = await api.get(`mobile/task/detail/${id}?user=${user}`);
return response.data;
};
export const apiEditTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string }, id: string }) => {
const response = await api.post(`/mobile/task/detail/${id}`, data)
return response.data;
};
export const apiDeleteTaskTugas = async (data: { user: string, idProject: string }, id: string) => {
const response = await api.delete(`mobile/task/detail/${id}`, { data })
return response.data
};
export const apiDeleteFileTask = async (data: { user: string }, id: string) => {
const response = await api.delete(`/mobile/task/file/${id}`, { data })
return response.data;
};
export const apiDeleteTaskMember = async (data: { user: string, idUser: string }, id: string) => {
const response = await api.delete(`mobile/task/${id}/member`, { data })
return response.data
};
export const apiCreateTaskTugas = async ({ data, id }: { data: { title: string, dateStart: string, user: string, dateEnd: string, idDivision: string }, id: string }) => {
const response = await api.post(`/mobile/task/${id}`, data)
return response.data;
};
export const apiCheckFileTask = async ({ data, id }: { data: FormData, id: string }) => {
const response = await api.put(`/mobile/task/file/${id}`, data,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
)
return response.data;
};
export const apiAddFileTask = async ({ data, id }: { data: FormData, id: string }) => {
const response = await api.post(`/mobile/task/file/${id}`, data,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
)
return response.data;
};
export const apiEditTask = async (data: { title: string, user: string }, id: string) => {
const response = await api.put(`/mobile/task/${id}`, 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
};
export const apiAddMemberTask = async ({ data, id }: { data: { user: string, member: any[], idDivision: string }, id: string }) => {
const response = await api.post(`/mobile/task/${id}/member`, data)
return response.data;
};

View File

@@ -13,6 +13,7 @@ import memberUpdate from './memberSlice';
import positionUpdate from './positionSlice';
import projectUpdate from './projectUpdate';
import taskCreate from './taskCreate';
import taskUpdate from './taskUpdate';
import userReducer from './userSlice';
const store = configureStore({
@@ -32,6 +33,7 @@ const store = configureStore({
divisionUpdate: divisionUpdate,
discussionUpdate: discussionUpdate,
calendarUpdate: calendarUpdate,
taskUpdate: taskUpdate,
}
});

20
lib/taskUpdate.ts Normal file
View File

@@ -0,0 +1,20 @@
import { createSlice } from '@reduxjs/toolkit';
const taskUpdate = createSlice({
name: 'taskUpdate',
initialState: {
data: false,
progress: false,
task: false,
file: false,
member: false
},
reducers: {
setUpdateTask: (state, action) => {
return action.payload;
},
},
});
export const { setUpdateTask } = taskUpdate.actions;
export default taskUpdate.reducer;