334 lines
14 KiB
TypeScript
334 lines
14 KiB
TypeScript
import AppHeader from "@/components/AppHeader"
|
||
import DrawerBottom from "@/components/drawerBottom"
|
||
import HeaderRightCalendarDetail from "@/components/calendar/headerCalendarDetail"
|
||
import ImageUser from "@/components/imageNew"
|
||
import MenuItemRow from "@/components/menuItemRow"
|
||
import ModalConfirmation from "@/components/ModalConfirmation"
|
||
import Text from "@/components/Text"
|
||
import { ConstEnv } from "@/constants/ConstEnv"
|
||
import Styles from "@/constants/Styles"
|
||
import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature } from "@/lib/api"
|
||
import { setUpdateCalendar } from "@/lib/calendarUpdate"
|
||
import { useAuthSession } from "@/providers/AuthProvider"
|
||
import { useTheme } from "@/providers/ThemeProvider"
|
||
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
|
||
import Clipboard from "@react-native-clipboard/clipboard"
|
||
import { router, Stack, useLocalSearchParams } from "expo-router"
|
||
import { useEffect, useState } from "react"
|
||
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
|
||
import Toast from "react-native-toast-message"
|
||
import { useDispatch, useSelector } from "react-redux"
|
||
|
||
type Props = {
|
||
id: string;
|
||
timeStart: string;
|
||
timeEnd: string;
|
||
dateStart: string;
|
||
dateEnd: string;
|
||
idCalendar: string;
|
||
status: number;
|
||
title: string;
|
||
desc: string;
|
||
linkMeet: string;
|
||
repeatEventTyper: string;
|
||
repeatValue: number;
|
||
}
|
||
|
||
type PropsMember = {
|
||
id: string;
|
||
idUser: string;
|
||
name: string;
|
||
img: string;
|
||
email: string
|
||
}
|
||
|
||
export default function DetailEventCalendar() {
|
||
const { colors } = useTheme()
|
||
const { id, detail } = useLocalSearchParams<{ id: string, detail: string }>();
|
||
const [data, setData] = useState<Props>()
|
||
const [member, setMember] = useState<PropsMember[]>([])
|
||
const { token, decryptToken } = useAuthSession();
|
||
const [memberChoose, setMemberChoose] = useState({ id: '', name: '' })
|
||
const [isModalMember, setModalMember] = useState(false)
|
||
const update = useSelector((state: any) => state.calendarUpdate)
|
||
const dispatch = useDispatch()
|
||
const entityUser = useSelector((state: any) => state.user);
|
||
const [isMemberDivision, setIsMemberDivision] = useState(false);
|
||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||
const [loading, setLoading] = useState(true)
|
||
const [refreshing, setRefreshing] = useState(false)
|
||
|
||
async function handleCheckMember() {
|
||
try {
|
||
const hasil = await decryptToken(String(token?.current));
|
||
const response = await apiGetDivisionOneFeature({
|
||
id,
|
||
user: hasil,
|
||
cat: "check-member",
|
||
});
|
||
|
||
setIsMemberDivision(response.data);
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
async function handleLoad(loading: boolean) {
|
||
try {
|
||
setLoading(loading)
|
||
const hasil = await decryptToken(String(token?.current));
|
||
const response = await apiGetCalendarOne({
|
||
user: hasil,
|
||
id: detail,
|
||
cat: 'data',
|
||
});
|
||
if (response.success) {
|
||
setData(response.data);
|
||
} else {
|
||
router.replace(`/division/${id}/calendar/`)
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
async function handleLoadMember() {
|
||
try {
|
||
const hasil = await decryptToken(String(token?.current));
|
||
const response = await apiGetCalendarOne({
|
||
user: hasil,
|
||
id: detail,
|
||
cat: 'member',
|
||
});
|
||
setMember(response.data);
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
handleLoad(true);
|
||
handleCheckMember()
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
handleLoadMember();
|
||
}, [update.member]);
|
||
|
||
const handleCopy = (text: string) => {
|
||
Clipboard.setString(text);
|
||
Toast.show({ type: 'small', text1: 'Berhasil menyalin link', })
|
||
};
|
||
|
||
async function handleDeleteUser() {
|
||
try {
|
||
const hasil = await decryptToken(String(token?.current));
|
||
const response = await apiDeleteCalendarMember({
|
||
user: hasil,
|
||
idUser: memberChoose.id,
|
||
}, String(data?.idCalendar));
|
||
|
||
if (response.success) {
|
||
dispatch(setUpdateCalendar({ ...update, member: !update.member }));
|
||
}
|
||
Toast.show({ type: 'small', text1: response.message, })
|
||
} catch (error: any) {
|
||
console.error(error);
|
||
const message = error?.response?.data?.message || "Gagal menghapus anggota"
|
||
|
||
Toast.show({ type: 'small', text1: message })
|
||
} finally {
|
||
setModalMember(false)
|
||
}
|
||
}
|
||
|
||
|
||
const handleRefresh = async () => {
|
||
setRefreshing(true)
|
||
handleLoad(false)
|
||
handleLoadMember()
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
setRefreshing(false)
|
||
};
|
||
|
||
const canManage = !((entityUser.role === "user" || entityUser.role === "coadmin") && !isMemberDivision)
|
||
|
||
const repeatLabel: Record<string, string> = {
|
||
once: 'Acara 1 Kali',
|
||
daily: 'Setiap Hari',
|
||
weekly: 'Mingguan',
|
||
monthly: 'Bulanan',
|
||
yearly: 'Tahunan',
|
||
}
|
||
|
||
function InfoRow({ icon, label, value, onCopy }: { icon: string, label: string, value?: string, onCopy?: () => void }) {
|
||
return (
|
||
<View style={[Styles.sectionActionRow, { paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: colors.icon + '14' }]}>
|
||
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
|
||
<MaterialCommunityIcons name={icon as any} size={18} color={colors.dimmed} />
|
||
</View>
|
||
<View style={Styles.flex1}>
|
||
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed, marginBottom: 2 }]}>{label}</Text>
|
||
{loading
|
||
? <View style={{ height: 13, borderRadius: 6, backgroundColor: colors.icon + '20', width: '70%' }} />
|
||
: <Text style={[Styles.textDefault, { color: colors.text }]}>{value || '-'}</Text>
|
||
}
|
||
</View>
|
||
{onCopy && !loading && value && (
|
||
<Pressable onPress={onCopy} style={{ padding: 4 }}>
|
||
<MaterialCommunityIcons name="content-copy" size={16} color={colors.dimmed} />
|
||
</Pressable>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}>
|
||
<Stack.Screen
|
||
options={{
|
||
header: () => (
|
||
<AppHeader
|
||
title="Detail Acara"
|
||
showBack={true}
|
||
onPressLeft={() => router.back()}
|
||
right={
|
||
(entityUser.role === "user" || entityUser.role === "coadmin") && !isMemberDivision
|
||
? <></> : <HeaderRightCalendarDetail id={String(data?.idCalendar)} idReminder={String(detail)} />
|
||
}
|
||
/>
|
||
)
|
||
}}
|
||
/>
|
||
<ScrollView
|
||
showsVerticalScrollIndicator={false}
|
||
style={Styles.h100}
|
||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={colors.icon} />}
|
||
>
|
||
<View style={[Styles.p15, Styles.mb100]}>
|
||
|
||
{/* Info acara */}
|
||
<View style={[Styles.wrapPaper, Styles.sectionCard, Styles.noShadow, Styles.mb15,
|
||
{ backgroundColor: colors.card, borderColor: colors.icon + '18', padding: 0, overflow: 'hidden' }]}>
|
||
<View style={{ padding: 16, borderBottomWidth: 1, borderBottomColor: colors.icon + '14' }}>
|
||
<View style={Styles.sectionActionRow}>
|
||
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
|
||
<MaterialCommunityIcons name="calendar-text" size={18} color={colors.dimmed} />
|
||
</View>
|
||
<Text style={[Styles.textDefaultSemiBold, Styles.flex1, { color: colors.text }]}>Detail Acara</Text>
|
||
</View>
|
||
</View>
|
||
<View style={{ paddingHorizontal: 16 }}>
|
||
<InfoRow icon="format-title" label="Judul" value={data?.title} />
|
||
<InfoRow icon="calendar-month-outline" label="Tanggal" value={data?.dateStart} />
|
||
<InfoRow icon="clock-outline" label="Waktu" value={data ? `${data.timeStart} – ${data.timeEnd}` : undefined} />
|
||
<InfoRow icon="repeat" label="Pengulangan" value={data?.repeatEventTyper ? repeatLabel[data.repeatEventTyper] : undefined} />
|
||
<InfoRow icon="link-variant" label="Link Meet" value={data?.linkMeet} onCopy={data?.linkMeet ? () => handleCopy(data.linkMeet) : undefined} />
|
||
<View style={[Styles.sectionActionRow, { paddingVertical: 10, alignItems: 'flex-start' }]}>
|
||
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
|
||
<MaterialCommunityIcons name="card-text-outline" size={18} color={colors.dimmed} />
|
||
</View>
|
||
<View style={Styles.flex1}>
|
||
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed, marginBottom: 2 }]}>Deskripsi</Text>
|
||
{loading
|
||
? <View style={{ height: 13, borderRadius: 6, backgroundColor: colors.icon + '20', width: '80%' }} />
|
||
: <Text style={[Styles.textDefault, { color: colors.text, lineHeight: 22 }]}>{data?.desc || '-'}</Text>
|
||
}
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Daftar anggota */}
|
||
<View style={[Styles.wrapPaper, Styles.sectionCard, Styles.noShadow,
|
||
{ backgroundColor: colors.card, borderColor: colors.icon + '18', padding: 0, overflow: 'hidden' }]}>
|
||
|
||
<View style={[Styles.sectionActionRow, { padding: 16, borderBottomWidth: 1, borderBottomColor: colors.icon + '14' }]}>
|
||
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
|
||
<MaterialIcons name="people" size={18} color={colors.dimmed} />
|
||
</View>
|
||
<Text style={[Styles.textDefaultSemiBold, Styles.flex1, { color: colors.text }]}>Anggota</Text>
|
||
<Text style={[Styles.textMediumNormal, { color: colors.dimmed }]}>{member.length} anggota</Text>
|
||
</View>
|
||
|
||
{member.length === 0
|
||
? (
|
||
<View style={[Styles.contentItemCenter, { paddingVertical: 40 }]}>
|
||
<MaterialIcons name="people-outline" size={34} color={colors.icon + '50'} />
|
||
<Text style={[Styles.textMediumNormal, Styles.mt10, { color: colors.dimmed }]}>Belum ada anggota</Text>
|
||
</View>
|
||
)
|
||
: member.map((item, index) => (
|
||
<Pressable
|
||
key={index}
|
||
onPress={() => {
|
||
if (!canManage) return
|
||
setMemberChoose({ id: item.idUser, name: item.name })
|
||
setModalMember(true)
|
||
}}
|
||
style={({ pressed }) => [
|
||
Styles.rowItemsCenter, Styles.ph15,
|
||
{
|
||
paddingVertical: 12, gap: 14,
|
||
borderBottomWidth: index < member.length - 1 ? 1 : 0,
|
||
borderBottomColor: colors.icon + '14',
|
||
backgroundColor: pressed && canManage ? colors.icon + '0E' : 'transparent',
|
||
},
|
||
]}
|
||
>
|
||
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
|
||
<View style={Styles.flex1}>
|
||
<Text style={[Styles.textDefault, { color: colors.text }]} numberOfLines={1}>{item.name}</Text>
|
||
<Text style={[Styles.textMediumNormal, { color: colors.dimmed }]} numberOfLines={1}>{item.email}</Text>
|
||
</View>
|
||
{canManage && <MaterialCommunityIcons name="chevron-right" size={18} color={colors.icon + '60'} />}
|
||
</Pressable>
|
||
))
|
||
}
|
||
</View>
|
||
|
||
</View>
|
||
</ScrollView>
|
||
|
||
|
||
<DrawerBottom animation="slide" isVisible={isModalMember} setVisible={setModalMember} title={memberChoose.name}>
|
||
<View style={Styles.rowItemsCenter}>
|
||
<MenuItemRow
|
||
icon={<MaterialCommunityIcons name="account-eye" color={colors.text} size={25} />}
|
||
title="Lihat Profil"
|
||
onPress={() => {
|
||
setModalMember(false)
|
||
router.push(`/member/${memberChoose.id}`)
|
||
}}
|
||
/>
|
||
|
||
<MenuItemRow
|
||
icon={<MaterialCommunityIcons name="account-remove" color={colors.text} size={25} />}
|
||
title="Keluarkan"
|
||
onPress={() => {
|
||
setModalMember(false)
|
||
setTimeout(() => {
|
||
setShowDeleteModal(true)
|
||
}, 600)
|
||
}}
|
||
/>
|
||
</View>
|
||
</DrawerBottom>
|
||
|
||
<ModalConfirmation
|
||
visible={showDeleteModal}
|
||
title="Konfirmasi"
|
||
message="Apakah Anda yakin ingin mengeluarkan anggota?"
|
||
onConfirm={() => {
|
||
setShowDeleteModal(false)
|
||
handleDeleteUser()
|
||
}}
|
||
onCancel={() => setShowDeleteModal(false)}
|
||
confirmText="Keluar"
|
||
cancelText="Batal"
|
||
/>
|
||
</SafeAreaView>
|
||
)
|
||
} |