From ecb3d3953b23532abccba950f3654587b1958eb6 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 18 May 2026 11:18:59 +0800 Subject: [PATCH 1/5] fix: rapikan import dan sesuaikan ukuran icon di notification - Gabungkan import useState yang duplikat - Kecilkan ukuran icon check-square dari 22 ke 20 --- app/(application)/notification.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/(application)/notification.tsx b/app/(application)/notification.tsx index 64322f9..03aa940 100644 --- a/app/(application)/notification.tsx +++ b/app/(application)/notification.tsx @@ -9,12 +9,11 @@ import { pushToPage } from "@/lib/pushToPage"; import { useAuthSession } from "@/providers/AuthProvider"; import { useTheme } from "@/providers/ThemeProvider"; import { Feather } from "@expo/vector-icons"; -import { router, Stack } from "expo-router"; import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; -import { useEffect, useMemo } from "react"; +import { router, Stack } from "expo-router"; +import { useEffect, useMemo, useState } from "react"; import { FlatList, Pressable, RefreshControl, SafeAreaView, View } from "react-native"; import { useDispatch, useSelector } from "react-redux"; -import { useState } from "react"; type Props = { id: string @@ -153,7 +152,7 @@ export default function Notification() { disabled={markingAll} style={{ opacity: markingAll ? 0.5 : 1, padding: 4 }} > - + ) : undefined } -- 2.49.1 From 90419b5d1578ba03e69dab8a0aa8df3488df65a0 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 18 May 2026 11:27:49 +0800 Subject: [PATCH 2/5] feat: tambah fitur tandai terbaca per notifikasi dan ekstrak styles - Tambah fungsi handleMarkOneRead untuk tandai satu notifikasi terbaca tanpa navigasi - Tambah tombol "Tandai dibaca" pada tiap notifikasi yang belum terbaca - Buat notification.styles.ts dengan 8 class styles untuk notification screen - Daftarkan NotificationStyles ke constants/styles/index.ts --- app/(application)/notification.tsx | 57 ++++++++++++++----------- constants/styles/index.ts | 2 + constants/styles/notification.styles.ts | 29 +++++++++++++ 3 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 constants/styles/notification.styles.ts diff --git a/app/(application)/notification.tsx b/app/(application)/notification.tsx index 03aa940..2bb50a9 100644 --- a/app/(application)/notification.tsx +++ b/app/(application)/notification.tsx @@ -136,6 +136,17 @@ export default function Notification() { } } + async function handleMarkOneRead(id: string) { + try { + const hasil = await decryptToken(String(token?.current)) + await apiReadOneNotification({ user: hasil, id: id }) + await queryClient.invalidateQueries({ queryKey: ['notifications'] }) + dispatch(setUpdateNotification(!updateNotification)) + } catch (error) { + console.error(error) + } + } + return ( setShowConfirm(false)} /> - + + {isLoading ? ( [0, 1, 2, 3, 4].map((_, i) => ) ) : flatData.length === 0 ? ( @@ -203,11 +215,11 @@ export default function Notification() { renderItem={({ item }) => { if (item._type === 'header') { return ( - - + + {item.date} - + ) } @@ -217,37 +229,20 @@ export default function Notification() { return ( handleReadNotification(item.id, item.category, item.idContent)} - style={({ pressed }) => [{ - flexDirection: 'row', - alignItems: 'center', - borderRadius: 10, - borderWidth: 1, + style={({ pressed }) => [Styles.notifItemRow, { borderColor: colors.icon + '20', backgroundColor: pressed ? colors.icon + '10' : item.isRead ? colors.icon + '10' : colors.card, - paddingHorizontal: 12, - paddingVertical: 10, - marginBottom: 6, }]} > - {/* Colored icon */} - + - {/* Content */} - + + {!item.isRead && ( + { + e.stopPropagation() + handleMarkOneRead(item.id) + }} + hitSlop={8} + style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, flexShrink: 0 })} + > + + Tandai dibaca + + + )} Date: Mon, 18 May 2026 11:37:04 +0800 Subject: [PATCH 3/5] fix: ubah label tombol "Pilih dari File Proyek" menjadi "Pilih dari File Kegiatan ini" --- .../[id]/(fitur-division)/task/[detail]/tugas-file/[taskId].tsx | 2 +- app/(application)/project/[id]/tugas-file/[taskId].tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(application)/division/[id]/(fitur-division)/task/[detail]/tugas-file/[taskId].tsx b/app/(application)/division/[id]/(fitur-division)/task/[detail]/tugas-file/[taskId].tsx index 2112500..7a6c724 100644 --- a/app/(application)/division/[id]/(fitur-division)/task/[detail]/tugas-file/[taskId].tsx +++ b/app/(application)/division/[id]/(fitur-division)/task/[detail]/tugas-file/[taskId].tsx @@ -251,7 +251,7 @@ export default function TugasFileScreen() { disabled={loadingUpload} /> { setSelectedProjectFiles([]); setPickerModal(true); diff --git a/app/(application)/project/[id]/tugas-file/[taskId].tsx b/app/(application)/project/[id]/tugas-file/[taskId].tsx index 3f47532..a84a0e8 100644 --- a/app/(application)/project/[id]/tugas-file/[taskId].tsx +++ b/app/(application)/project/[id]/tugas-file/[taskId].tsx @@ -246,7 +246,7 @@ export default function ProjectTugasFileScreen() { disabled={loadingUpload} /> { setSelectedProjectFiles([]); setPickerModal(true); -- 2.49.1 From 3f113a4049d988b0baf5e941ad6494a80ca3ad2d Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 18 May 2026 11:43:16 +0800 Subject: [PATCH 4/5] fix: urutkan grup tanggal notifikasi dari terbaru ke terlama --- app/(application)/notification.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/(application)/notification.tsx b/app/(application)/notification.tsx index 2bb50a9..6346a4b 100644 --- a/app/(application)/notification.tsx +++ b/app/(application)/notification.tsx @@ -78,6 +78,15 @@ export default function Notification() { }, [data]) const listData = useMemo(() => { + const BULAN: Record = { + 'JAN': 0, 'FEB': 1, 'MAR': 2, 'APR': 3, 'MEI': 4, 'JUN': 5, + 'JUL': 6, 'AGU': 7, 'SEP': 8, 'OKT': 9, 'NOV': 10, 'DES': 11, + } + const parseDate = (str: string) => { + const [d, m, y] = str.split(' ') + return new Date(Number(y), BULAN[m] ?? 0, Number(d)).getTime() + } + const groups: Record = {} const dateOrder: string[] = [] @@ -89,6 +98,8 @@ export default function Notification() { groups[item.createdAt].push(item) }) + dateOrder.sort((a, b) => parseDate(b) - parseDate(a)) + const result: ListRow[] = [] dateOrder.forEach((date) => { result.push({ _type: 'header', date }) -- 2.49.1 From 85aca330e5c70dbe0631c67b84a7582a82ed49ef Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 18 May 2026 14:52:30 +0800 Subject: [PATCH 5/5] feat: filter approval berdasarkan group dan perbaikan tampilan riwayat - Simpan idGroup user ke Redux saat login agar perbandingan group bisa dilakukan - Filter button persetujuan project: isApprover hanya tampil jika group sama - Filter button persetujuan division/task: isApprover hanya tampil jika group sama - Pass idGroup ke SectionTanggalTugasProject dan SectionTanggalTugasTask dari parent - Samakan warna icon, label, dan nama pada riwayat persetujuan - Ubah bg alasan penolakan dari merah ke netral, label tetap merah - Ekstrak inline styles ModalRiwayatApproval ke approval.styles.ts --- .../(fitur-division)/task/[detail]/index.tsx | 3 +- app/(application)/project/[id]/index.tsx | 2 +- components/ModalRiwayatApproval.tsx | 39 ++++++------------- components/home/carouselHome.tsx | 2 +- components/home/carouselHome2.tsx | 2 +- components/project/sectionTanggalTugas.tsx | 4 +- components/task/sectionTanggalTugasTask.tsx | 4 +- constants/styles/approval.styles.ts | 13 +++++++ constants/styles/index.ts | 2 + 9 files changed, 35 insertions(+), 36 deletions(-) create mode 100644 constants/styles/approval.styles.ts diff --git a/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx b/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx index 2e9eb93..d4ac8f3 100644 --- a/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/task/[detail]/index.tsx @@ -26,6 +26,7 @@ type Props = { reason: string status: number isActive: boolean + idGroup: string } export default function DetailTaskDivision() { @@ -159,7 +160,7 @@ export default function DetailTaskDivision() { } - + diff --git a/app/(application)/project/[id]/index.tsx b/app/(application)/project/[id]/index.tsx index 9fe7b47..a57d685 100644 --- a/app/(application)/project/[id]/index.tsx +++ b/app/(application)/project/[id]/index.tsx @@ -150,7 +150,7 @@ export default function DetailProject() { } - + diff --git a/components/ModalRiwayatApproval.tsx b/components/ModalRiwayatApproval.tsx index 3cc8523..0dbd256 100644 --- a/components/ModalRiwayatApproval.tsx +++ b/components/ModalRiwayatApproval.tsx @@ -33,13 +33,7 @@ function ApprovalStatusBadge({ status }: { status: number }) { : { label: 'Menunggu', color: '#FFA94D' } return ( - + {config.label} @@ -79,16 +73,10 @@ export default function ModalRiwayatApproval({ isVisible, setVisible, data, load data.map((item, index) => ( {/* Status + tanggal */} - + {item.createdAt} @@ -97,15 +85,15 @@ export default function ModalRiwayatApproval({ isVisible, setVisible, data, load {/* Pengaju */} - - Diajukan Oleh: + + Diajukan Oleh: {item.submitter.name} {/* Approver */} - - Disetujui Oleh: + + Disetujui Oleh: {item.approver?.name ?? '-'} @@ -113,16 +101,11 @@ export default function ModalRiwayatApproval({ isVisible, setVisible, data, load {/* Catatan penolakan */} {item.note && ( - - + + Alasan Penolakan - + {item.note} @@ -130,7 +113,7 @@ export default function ModalRiwayatApproval({ isVisible, setVisible, data, load )) ) : ( - + Belum ada riwayat persetujuan )} diff --git a/components/home/carouselHome.tsx b/components/home/carouselHome.tsx index 80c5e20..a0aed3c 100644 --- a/components/home/carouselHome.tsx +++ b/components/home/carouselHome.tsx @@ -36,7 +36,7 @@ export default function CaraouselHome({ refreshing }: { refreshing: boolean }) { async function handleUser() { const hasil = await decryptToken(String(token?.current)) const response = await apiGetProfile({ id: hasil }) - dispatch(setEntityUser({ role: response.data.idUserRole, admin: false, isApprover: response.data.isApprover ?? false })) + dispatch(setEntityUser({ role: response.data.idUserRole, admin: false, isApprover: response.data.isApprover ?? false, idGroup: response.data.idGroup ?? '' })) } useEffect(() => { diff --git a/components/home/carouselHome2.tsx b/components/home/carouselHome2.tsx index 501385f..8381596 100644 --- a/components/home/carouselHome2.tsx +++ b/components/home/carouselHome2.tsx @@ -59,7 +59,7 @@ export default function CaraouselHome2({ refreshing }: { refreshing: boolean }) // Sync User Role to Redux useEffect(() => { if (profile) { - dispatch(setEntityUser({ role: profile.idUserRole, admin: false, isApprover: profile.isApprover ?? false })) + dispatch(setEntityUser({ role: profile.idUserRole, admin: false, isApprover: profile.isApprover ?? false, idGroup: profile.idGroup ?? '' })) } }, [profile, dispatch]) diff --git a/components/project/sectionTanggalTugas.tsx b/components/project/sectionTanggalTugas.tsx index 19483f6..21534ad 100644 --- a/components/project/sectionTanggalTugas.tsx +++ b/components/project/sectionTanggalTugas.tsx @@ -39,7 +39,7 @@ type ApprovalRecord = { createdAt: string } -export default function SectionTanggalTugasProject({ status, member, refreshing }: { status: number | undefined, member: boolean, refreshing?: boolean }) { +export default function SectionTanggalTugasProject({ status, member, refreshing, idGroup }: { status: number | undefined, member: boolean, refreshing?: boolean, idGroup: string }) { const { colors } = useTheme(); const entityUser = useSelector((state: any) => state.user) const dispatch = useDispatch() @@ -61,7 +61,7 @@ export default function SectionTanggalTugasProject({ status, member, refreshing const [tugas, setTugas] = useState({ id: '', status: 0 }) const [showDeleteModal, setShowDeleteModal] = useState(false) - const isApprover = entityUser.isApprover || ['supadmin', 'developer'].includes(entityUser.role) + const isApprover = (entityUser.isApprover && entityUser.idGroup === idGroup) || ['supadmin', 'developer'].includes(entityUser.role) const isAdmin = entityUser.role !== 'user' && entityUser.role !== 'coadmin' async function handleLoad(loading: boolean) { diff --git a/components/task/sectionTanggalTugasTask.tsx b/components/task/sectionTanggalTugasTask.tsx index 5a4e013..9783abf 100644 --- a/components/task/sectionTanggalTugasTask.tsx +++ b/components/task/sectionTanggalTugasTask.tsx @@ -38,7 +38,7 @@ type ApprovalRecord = { createdAt: string } -export default function SectionTanggalTugasTask({ refreshing, isMemberDivision, isAdminDivision, status }: { refreshing: boolean, isMemberDivision: boolean, isAdminDivision: boolean, status?: number }) { +export default function SectionTanggalTugasTask({ refreshing, isMemberDivision, isAdminDivision, status, idGroup }: { refreshing: boolean, isMemberDivision: boolean, isAdminDivision: boolean, status?: number, idGroup: string }) { const { colors } = useTheme() const dispatch = useDispatch() const entityUser = useSelector((state: any) => state.user); @@ -60,7 +60,7 @@ export default function SectionTanggalTugasTask({ refreshing, isMemberDivision, const [tugas, setTugas] = useState({ id: '', status: 0 }) const [showDeleteModal, setShowDeleteModal] = useState(false) - const isApprover = entityUser.isApprover || ['supadmin', 'developer'].includes(entityUser.role) + const isApprover = (entityUser.isApprover && entityUser.idGroup === idGroup) || ['supadmin', 'developer'].includes(entityUser.role) const isAdmin = entityUser.role !== 'user' && entityUser.role !== 'coadmin' const canTakeAction = isMemberDivision || isAdmin diff --git a/constants/styles/approval.styles.ts b/constants/styles/approval.styles.ts new file mode 100644 index 0000000..17254dc --- /dev/null +++ b/constants/styles/approval.styles.ts @@ -0,0 +1,13 @@ +import { StyleSheet } from "react-native"; + +const ApprovalStyles = StyleSheet.create({ + approvalBadge: { borderRadius: 20, paddingHorizontal: 10, paddingVertical: 3, alignSelf: 'flex-start' }, + approvalItem: { borderWidth: 1, borderRadius: 10, padding: 12, marginBottom: 10 }, + approvalItemHeader: { justifyContent: 'space-between', marginBottom: 8 }, + approvalIconMr: { marginRight: 6 }, + approvalNoteBox: { borderRadius: 8, padding: 8, marginTop: 4 }, + approvalNoteLabel: { marginBottom: 2 }, + approvalEmptyText: { textAlign: 'center' }, +}); + +export default ApprovalStyles; diff --git a/constants/styles/index.ts b/constants/styles/index.ts index 2047cc0..226f46c 100644 --- a/constants/styles/index.ts +++ b/constants/styles/index.ts @@ -10,6 +10,7 @@ import ModalStyles from './modal.styles'; import HeaderStyles from './header.styles'; import ComponentStyles from './component.styles'; import NotificationStyles from './notification.styles'; +import ApprovalStyles from './approval.styles'; const Styles = StyleSheet.create({ ...SpacingStyles, @@ -23,6 +24,7 @@ const Styles = StyleSheet.create({ ...HeaderStyles, ...ComponentStyles, ...NotificationStyles, + ...ApprovalStyles, }); export default Styles; -- 2.49.1