diff --git a/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/edit.tsx b/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/edit.tsx index 5afd376..01afec3 100644 --- a/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/edit.tsx +++ b/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/edit.tsx @@ -1,31 +1,47 @@ import ButtonBackHeader from "@/components/buttonBackHeader" import ButtonSaveHeader from "@/components/buttonSaveHeader" +import { InputDate } from "@/components/inputDate" import { InputForm } from "@/components/inputForm" +import ModalSelect from "@/components/modalSelect" import SelectForm from "@/components/selectForm" import Styles from "@/constants/Styles" -import { apiGetCalendarOne } from "@/lib/api" +import { valueTypeEventRepeat } from "@/constants/TypeEventRepeat" +import { apiGetCalendarOne, apiUpdateCalendar } from "@/lib/api" +import { setUpdateCalendar } from "@/lib/calendarUpdate" +import { stringToDateTime } from "@/lib/fun_stringToDate" import { useAuthSession } from "@/providers/AuthProvider" import { Stack, router, useLocalSearchParams } from "expo-router" +import moment from "moment" import { useEffect, useState } from "react" -import { SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native" +import { SafeAreaView, ScrollView, ToastAndroid, View } from "react-native" +import { useDispatch, useSelector } from "react-redux" export default function EditEventCalendar() { const { token, decryptToken } = useAuthSession(); - const [chooseGroup, setChooseGroup] = useState({ val: '', label: '' }) - const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>(); + const [choose, setChoose] = useState({ val: "", label: "" }) + const [isSelect, setSelect] = useState(false) + const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>() + const [idCalendar, setIdCalendar] = useState('') + const dispatch = useDispatch() + const update = useSelector((state: any) => state.updateCalendar) + + const [error, setError] = useState({ + title: false, + dateStart: false, + timeStart: false, + timeEnd: false, + repeatEventType: false, + repeatValue: false, + }); const [data, setData] = useState({ - id: '', timeStart: '', timeEnd: '', dateStart: '', - dateEnd: '', - idCalendar: '', - status: 0, title: '', desc: '', linkMeet: '', repeatEventTyper: '', - repeatValue: 0, + repeatValue: 1, }) async function handleLoad() { @@ -36,9 +52,14 @@ export default function EditEventCalendar() { id: detail, cat: 'data', }); - setData(response.data); + setData(response.data) + setIdCalendar(response.data.idCalendar) + console.log(response.data) + setData({ ...data, dateStart: moment(response.data.dateStartFormat, 'DD-MM-YYYY').format('DD-MM-YYYY') }) + setChoose({ val: response.data.repeatEventTyper, label: valueTypeEventRepeat.find((item) => item.id == response.data.repeatEventTyper)?.name || "" }) } catch (error) { console.error(error); + ToastAndroid.show('Gagal mendapatkan data', ToastAndroid.SHORT) } } @@ -48,6 +69,99 @@ export default function EditEventCalendar() { + function validationForm(cat: "title" | "desc" | "dateStart" | "timeStart" | "timeEnd" | "repeatEventType" | "repeatValue" | "linkMeet", val: string, label?: string) { + if (cat == "title") { + setData({ ...data, title: val }); + if (val == "" || val == "null") { + setError({ ...error, title: true }); + } else { + setError({ ...error, title: false }); + } + } else if (cat == "desc") { + setData({ ...data, desc: val }); + } else if (cat == "dateStart") { + setData({ ...data, dateStart: val }); + if (val == "" || val == "null") { + setError({ ...error, dateStart: true }); + } else { + setError({ ...error, dateStart: false }); + } + } else if (cat == "timeStart") { + setData({ ...data, timeStart: val }); + if (val == "" || val == "null") { + setError({ ...error, timeStart: true }); + } else { + setError({ ...error, timeStart: false }); + } + const start = stringToDateTime(data.dateStart, data.timeStart); + const end = stringToDateTime(data.dateStart, data.timeEnd); + const timestampAwal = start.getTime(); + const timestampAkhir = end.getTime(); + + if (timestampAwal > timestampAkhir) { + setError({ ...error, timeEnd: true }); + } else { + setError({ ...error, timeEnd: false }); + } + } else if (cat == "timeEnd") { + setData({ ...data, timeEnd: val }); + if (val == "" || val == "null") { + setError({ ...error, timeEnd: true }); + } else { + setError({ ...error, timeEnd: false }); + } + const start = stringToDateTime(data.dateStart, data.timeStart); + const end = stringToDateTime(data.dateStart, val); + const timestampAwal = start.getTime(); + const timestampAkhir = end.getTime(); + + if (timestampAwal > timestampAkhir) { + setError({ ...error, timeEnd: true }); + } else { + setError({ ...error, timeEnd: false }); + } + } else if (cat == "repeatEventType") { + setChoose({ val, label: String(label) }) + setData({ ...data, repeatEventTyper: val }); + if (val == "" || val == "null") { + setError({ ...error, repeatEventType: true }); + } else { + setError({ ...error, repeatEventType: false }); + if (val == "once") { + setData({ ...data, repeatValue: 1 }); + } + } + } else if (cat == "repeatValue") { + setData({ ...data, repeatValue: Number(val) }); + if (val == "" || val == "null") { + setError({ ...error, repeatValue: true }); + } else { + setError({ ...error, repeatValue: false }); + } + } else if (cat == "linkMeet") { + setData({ ...data, linkMeet: val }); + } + } + + async function handleUpdate() { + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiUpdateCalendar({ data: { ...data, user: hasil }, id: idCalendar }) + if (response.success) { + ToastAndroid.show('Berhasil mengubah acara', ToastAndroid.SHORT) + dispatch(setUpdateCalendar({ ...update, data: !update.data })); + router.replace(`/division/${id}/calendar/`) + } else { + ToastAndroid.show(response.message, ToastAndroid.SHORT) + } + } catch (error) { + console.error(error) + ToastAndroid.show('Gagal mengubah acara', ToastAndroid.SHORT) + } + } + + + return ( { router.back() }} />, headerTitle: 'Edit Acara', headerTitleAlign: 'center', - headerRight: () => { - ToastAndroid.show('Berhasil mengubah data', ToastAndroid.SHORT) - router.back() - }} /> + headerRight: () => + val == true) || data.title == "" || data.dateStart == "" || data.timeStart == "" || data.timeEnd == "" || data.repeatEventTyper == ""} + category="update" + onPress={() => { + handleUpdate() + }} + /> }} /> - - + validationForm("title", val)} + error={error.title} + errorText="Nama acara tidak boleh kosong" + /> + validationForm("dateStart", val)} + mode="date" + value={data.dateStart} + label="Tanggal Acara" + required + error={error.dateStart} + errorText="Tanggal acara tidak boleh kosong" + placeholder="Pilih Tanggal Acara" + /> - - Waktu Awal* - - --.-- - + + validationForm("timeStart", val)} + mode="time" + value={data.timeStart} + label="Waktu Awal" + required + error={error.timeStart} + errorText="Waktu awal tidak valid" + placeholder="--:--" + /> - - Waktu Akhir * - - --.-- - + + validationForm("timeEnd", val)} + mode="time" + value={data.timeEnd} + label="Waktu Akhir" + required + error={error.timeEnd} + errorText="Waktu akhir tidak valid" + placeholder="--:--" + /> - - { }} /> - - + validationForm("linkMeet", val)} + /> + { setSelect(true) }} + /> + validationForm("repeatValue", val)} + error={error.repeatValue} + errorText="Jumlah pengulangan tidak valid" + disable={choose.val == "once"} + /> + validationForm("desc", val)} + multiline + /> + + { + validationForm("repeatEventType", value.val, value.label); + }} + title={"Ulangi Acara"} + open={isSelect} + valChoose={choose.val} + /> ) } \ No newline at end of file diff --git a/app/(application)/division/[id]/(fitur-division)/calendar/create-member.tsx b/app/(application)/division/[id]/(fitur-division)/calendar/create-member.tsx new file mode 100644 index 0000000..87ae615 --- /dev/null +++ b/app/(application)/division/[id]/(fitur-division)/calendar/create-member.tsx @@ -0,0 +1,157 @@ +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 { apiCreateCalendar, apiGetDivisionMember } from "@/lib/api"; +import { setFormCreateCalendar } from "@/lib/calendarCreate"; +import { setUpdateCalendar } from "@/lib/calendarUpdate"; +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 CreateCalendarAddMember() { + const { token, decryptToken } = useAuthSession() + const { id } = useLocalSearchParams<{ id: string }>() + const [data, setData] = useState([]) + const [selectMember, setSelectMember] = useState([]) + const [search, setSearch] = useState('') + const update = useSelector((state: any) => state.calendarCreate) + const dispatch = useDispatch() + const updateRefresh = useSelector((state: any) => state.calendarUpdate) + + + + async function handleLoadMemberDivision() { + const hasil = await decryptToken(String(token?.current)) + const responMemberDivision = await apiGetDivisionMember({ id: id, user: hasil, search: search }) + setData(responMemberDivision.data) + if (update.member.length > 0) { + setSelectMember(update.member) + } + } + + 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() { + dispatch(setFormCreateCalendar({ ...update, member: selectMember })) + try { + const hasil = await decryptToken(String(token?.current)) + const response = await apiCreateCalendar({ data: { ...update, user: hasil, idDivision: id } }) + if (response.success) { + ToastAndroid.show('Berhasil membuat acara', ToastAndroid.SHORT) + dispatch(setFormCreateCalendar({ + title: "", + desc: "", + dateStart: "", + timeStart: "", + timeEnd: "", + repeatEventType: "", + repeatValue: 1, + linkMeet: "", + member: [], + })) + dispatch(setUpdateCalendar({ ...updateRefresh, data: !updateRefresh.data })); + router.replace(`/division/${id}/calendar`) + } else { + ToastAndroid.show(response.message, ToastAndroid.SHORT) + } + } catch (error) { + console.error(error) + ToastAndroid.show('Gagal membuat acara', ToastAndroid.SHORT) + } + } + + return ( + + { router.back() }} />, + headerTitle: 'Pilih Anggota', + headerTitleAlign: 'center', + headerRight: () => ( + 0 ? false : true} + onPress={() => { handleAddMember() }} + /> + ) + }} + /> + + setSearch(val)} value={search} /> + + { + selectMember.length > 0 + ? + + + { + selectMember.map((item: any, index: any) => ( + onChoose(item.idUser, item.name, item.img)} + /> + )) + } + + + + : + Tidak ada member yang dipilih + } + + + { + data.length > 0 ? + data.map((item: any, index: any) => { + return ( + {onChoose(item.idUser, item.name, item.img) }} + > + + + + {item.name} + + + { + selectMember.some((i: any) => i.idUser == item.idUser) && + } + + ) + } + ) + : + Tidak ada data + } + + + + ) +} \ No newline at end of file diff --git a/app/(application)/division/[id]/(fitur-division)/calendar/create.tsx b/app/(application)/division/[id]/(fitur-division)/calendar/create.tsx index af63c31..062982e 100644 --- a/app/(application)/division/[id]/(fitur-division)/calendar/create.tsx +++ b/app/(application)/division/[id]/(fitur-division)/calendar/create.tsx @@ -1,22 +1,27 @@ import ButtonBackHeader from "@/components/buttonBackHeader"; -import ButtonSaveHeader from "@/components/buttonSaveHeader"; +import ButtonNextHeader from "@/components/buttonNextHeader"; import { InputDate } from "@/components/inputDate"; import { InputForm } from "@/components/inputForm"; +import ModalSelect from "@/components/modalSelect"; import SelectForm from "@/components/selectForm"; import Styles from "@/constants/Styles"; -import { stringToDate } from "@/lib/fun_stringToDate"; +import { setFormCreateCalendar } from "@/lib/calendarCreate"; +import { stringToDateTime } from "@/lib/fun_stringToDate"; import { Stack, router, useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { SafeAreaView, ScrollView, - ToastAndroid, View } from "react-native"; +import { useDispatch, useSelector } from "react-redux"; export default function CalendarDivisionCreate() { const { id } = useLocalSearchParams<{ id: string }>() - const [chooseGroup, setChooseGroup] = useState({ val: "", label: "" }); + const [choose, setChoose] = useState({ val: "", label: "" }) + const [isSelect, setSelect] = useState(false) + const update = useSelector((state: any) => state.calendarCreate) + const dispatch = useDispatch() const [error, setError] = useState({ title: false, dateStart: false, @@ -36,7 +41,7 @@ export default function CalendarDivisionCreate() { linkMeet: "", }); - function validationForm(cat: "title" | "desc" | "dateStart" | "timeStart" | "timeEnd" | "repeatEventType" | "repeatValue" | "linkMeet", val: string) { + function validationForm(cat: "title" | "desc" | "dateStart" | "timeStart" | "timeEnd" | "repeatEventType" | "repeatValue" | "linkMeet", val: string, label?: string) { if (cat == "title") { setData({ ...data, title: val }); if (val == "" || val == "null") { @@ -60,9 +65,12 @@ export default function CalendarDivisionCreate() { } else { setError({ ...error, timeStart: false }); } - const start = stringToDate(val); - const end = stringToDate(data.timeEnd); - if (start < end) { + const start = stringToDateTime(data.dateStart, data.timeStart); + const end = stringToDateTime(data.dateStart, data.timeEnd); + const timestampAwal = start.getTime(); + const timestampAkhir = end.getTime(); + + if (timestampAwal > timestampAkhir) { setError({ ...error, timeEnd: true }); } else { setError({ ...error, timeEnd: false }); @@ -74,19 +82,26 @@ export default function CalendarDivisionCreate() { } else { setError({ ...error, timeEnd: false }); } - const start = stringToDate(data.timeStart); - const end = stringToDate(val); - if (start < end) { + const start = stringToDateTime(data.dateStart, data.timeStart); + const end = stringToDateTime(data.dateStart, val); + const timestampAwal = start.getTime(); + const timestampAkhir = end.getTime(); + + if (timestampAwal > timestampAkhir) { setError({ ...error, timeEnd: true }); } else { setError({ ...error, timeEnd: false }); } } else if (cat == "repeatEventType") { + setChoose({ val, label: String(label) }) setData({ ...data, repeatEventType: val }); if (val == "" || val == "null") { setError({ ...error, repeatEventType: true }); } else { setError({ ...error, repeatEventType: false }); + if (val == "once") { + setData({ ...data, repeatValue: 1 }); + } } } else if (cat == "repeatValue") { setData({ ...data, repeatValue: Number(val) }); @@ -100,6 +115,11 @@ export default function CalendarDivisionCreate() { } } + function handleSetData() { + dispatch(setFormCreateCalendar(data)) + router.push(`./create-member`) + } + return ( ( - { - ToastAndroid.show( - "Berhasil menambahkan data", - ToastAndroid.SHORT - ); - router.push("./"); - }} + { handleSetData() }} + disable={Object.values(error).some((val) => val == true) || data.title == "" || data.dateStart == "" || data.timeStart == "" || data.timeEnd == "" || data.repeatEventType == ""} /> ), }} @@ -159,7 +173,7 @@ export default function CalendarDivisionCreate() { label="Waktu Awal" required error={error.timeStart} - errorText="Waktu awal tidak boleh kosong" + errorText="Waktu awal tidak valid" placeholder="--:--" /> @@ -171,7 +185,7 @@ export default function CalendarDivisionCreate() { label="Waktu Akhir" required error={error.timeEnd} - errorText="Waktu akhir tidak boleh kosong" + errorText="Waktu akhir tidak valid" placeholder="--:--" /> @@ -188,9 +202,9 @@ export default function CalendarDivisionCreate() { bg="white" label="Ulangi Acara" placeholder="Ulangi Acara" - value={chooseGroup.label} + value={choose.label} required - onPress={() => { }} + onPress={() => { setSelect(true) }} /> validationForm("repeatValue", val)} + error={error.repeatValue} + errorText="Jumlah pengulangan tidak valid" + disable={choose.val == "once"} /> + + { + validationForm("repeatEventType", value.val, value.label); + }} + title={"Ulangi Acara"} + open={isSelect} + valChoose={choose.val} + /> ); } diff --git a/components/calendar/headerCalendarDetail.tsx b/components/calendar/headerCalendarDetail.tsx index 88ee69e..e173af9 100644 --- a/components/calendar/headerCalendarDetail.tsx +++ b/components/calendar/headerCalendarDetail.tsx @@ -60,7 +60,7 @@ export default function HeaderRightCalendarDetail({ id, idReminder }: Props) { title="Edit" onPress={() => { setVisible(false) - router.push(`./${id}/edit`) + router.push(`./${idReminder}/edit`) }} /> void title: string - category: 'group' | 'status-task' | 'position' | 'role' | 'gender' | 'member' + category: 'group' | 'status-task' | 'position' | 'role' | 'gender' | 'member' | 'type-event-repeat' idParent?: string onSelect: (value: { val: string, label: string }) => void valChoose?: string @@ -80,6 +81,10 @@ export default function ModalSelect({ open, close, title, category, idParent, on setData(valueGender) } + function handleLoadTypeEventRepeat() { + setData(valueTypeEventRepeat) + } + useEffect(() => { if (category == 'group') { @@ -94,6 +99,8 @@ export default function ModalSelect({ open, close, title, category, idParent, on handleLoadGender() } else if (category == "member") { handleLoadMember() + } else if (category == "type-event-repeat") { + handleLoadTypeEventRepeat() } setChooseValue({ ...chooseValue, val: valChoose }) }, [dispatch, open, search]); diff --git a/constants/TypeEventRepeat.ts b/constants/TypeEventRepeat.ts new file mode 100644 index 0000000..2968aa2 --- /dev/null +++ b/constants/TypeEventRepeat.ts @@ -0,0 +1,8 @@ +export const valueTypeEventRepeat = + [ + { id: 'once', name: 'Acara 1 Kali' }, + { id: 'daily', name: 'Setiap Hari' }, + { id: 'weekly', name: 'Mingguan' }, + { id: 'monthly', name: 'Bulanan' }, + { id: 'yearly', name: 'Tahunan' }, + ] \ No newline at end of file diff --git a/lib/api.ts b/lib/api.ts index d42d787..0a22269 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -2,8 +2,8 @@ import axios from 'axios'; const api = axios.create({ // baseURL: 'http://10.0.2.2:3000/api', - baseURL: 'https://stg-darmasaba.wibudev.com/api', - // baseURL: 'http://192.168.1.126:3000/api', + // baseURL: 'https://stg-darmasaba.wibudev.com/api', + baseURL: 'http://192.168.1.126:3000/api', }); export const apiCheckPhoneLogin = async (body: { phone: string }) => { @@ -487,6 +487,16 @@ export const apiGetCalendarHistory = async ({ user, search, division, page }: { return response.data; }; +export const apiCreateCalendar = async ({ data }: { data: { idDivision: string, title: string, desc: string, timeStart: string, timeEnd: string, dateStart: string, repeatEventTyper: string, repeatValue: string, linkMeet: string, member: any[], user: string } }) => { + const response = await api.post(`/mobile/calendar`, data) + return response.data; +}; + +export const apiUpdateCalendar = async ({ data, id }: { data: { title: string, desc: string, timeStart: string, timeEnd: string, dateStart: string, repeatEventTyper: string, repeatValue: number, linkMeet: string, user: string }, id: string }) => { + const response = await api.put(`/mobile/calendar/${id}`, data) + return response.data; +}; + export const apiGetTask = async ({ user, status, search, division, page }: { user: string, status: string, search: string, division: string, page?: number }) => { const response = await api.get(`mobile/task?user=${user}&status=${status}&division=${division}&search=${search}&page=${page}`); return response.data; diff --git a/lib/calendarCreate.ts b/lib/calendarCreate.ts new file mode 100644 index 0000000..b45844d --- /dev/null +++ b/lib/calendarCreate.ts @@ -0,0 +1,24 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const calendarCreate = createSlice({ + name: 'calendarCreate', + initialState: { + title: "", + desc: "", + dateStart: "", + timeStart: "", + timeEnd: "", + repeatEventType: "", + repeatValue: 1, + linkMeet: "", + member: [], + }, + reducers: { + setFormCreateCalendar: (state, action) => { + return action.payload; + }, + }, +}); + +export const { setFormCreateCalendar } = calendarCreate.actions; +export default calendarCreate.reducer; \ No newline at end of file diff --git a/lib/fun_stringToDate.ts b/lib/fun_stringToDate.ts index ac4e420..fa28681 100644 --- a/lib/fun_stringToDate.ts +++ b/lib/fun_stringToDate.ts @@ -1,4 +1,17 @@ +import moment from "moment"; + export function stringToDate(date: string) { const [dd, mm, yyyy] = date.split('-'); return new Date(Number(yyyy), Number(mm) - 1, Number(dd)) +} + +export function stringToDateTime(date: string, time: string) { + if (date == "" || date == "null" || date == undefined) { + date = moment().format('DD-MM-YYYY') + } + + const [dd, mm, yyyy] = date.split('-'); + const [hh, mi] = time.split(':'); + + return new Date(Number(yyyy), Number(mm) - 1, Number(dd), Number(hh), Number(mi)) } \ No newline at end of file diff --git a/lib/store.ts b/lib/store.ts index 661d053..2763c8a 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -1,6 +1,7 @@ import { configureStore } from '@reduxjs/toolkit'; import announcementUpdate from './announcementUpdate'; import bannerReducer from './bannerSlice'; +import calendarCreate from './calendarCreate'; import calendarUpdate from './calendarUpdate'; import discussionGeneralDetailUpdate from './discussionGeneralDetail'; import discussionUpdate from './discussionUpdate'; @@ -40,6 +41,7 @@ const store = configureStore({ divisionCreate: divisionCreate, dokumenUpdate: dokumenUpdate, notificationUpdate: notificationUpdate, + calendarCreate: calendarCreate, } });