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)/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)/notification.tsx b/app/(application)/notification.tsx
index 64322f9..6346a4b 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
@@ -79,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[] = []
@@ -90,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 })
@@ -137,6 +147,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 (
-
+
) : undefined
}
@@ -175,7 +196,8 @@ export default function Notification() {
onCancel={() => setShowConfirm(false)}
/>
-
+
+
{isLoading ? (
[0, 1, 2, 3, 4].map((_, i) => )
) : flatData.length === 0 ? (
@@ -204,11 +226,11 @@ export default function Notification() {
renderItem={({ item }) => {
if (item._type === 'header') {
return (
-
-
+
+
{item.date}
-
+
)
}
@@ -218,37 +240,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
+
+
+ )}
-
+
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);
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 dbae1b1..226f46c 100644
--- a/constants/styles/index.ts
+++ b/constants/styles/index.ts
@@ -9,6 +9,8 @@ import CardStyles from './card.styles';
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,
@@ -21,6 +23,8 @@ const Styles = StyleSheet.create({
...ModalStyles,
...HeaderStyles,
...ComponentStyles,
+ ...NotificationStyles,
+ ...ApprovalStyles,
});
export default Styles;
diff --git a/constants/styles/notification.styles.ts b/constants/styles/notification.styles.ts
new file mode 100644
index 0000000..0519fbc
--- /dev/null
+++ b/constants/styles/notification.styles.ts
@@ -0,0 +1,29 @@
+import { StyleSheet } from "react-native";
+
+const NotificationStyles = StyleSheet.create({
+ notifContainer: { paddingTop: 10 },
+ notifHeaderRow: { marginTop: 16, marginBottom: 8 },
+ notifDateSeparator: { flex: 1, height: 1, marginLeft: 8 },
+ notifDateText: { fontSize: 11, fontWeight: '600', letterSpacing: 0.6, textTransform: 'uppercase' },
+ notifItemRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: 10,
+ borderWidth: 1,
+ paddingHorizontal: 12,
+ paddingVertical: 10,
+ marginBottom: 6,
+ },
+ notifIconContainer: {
+ width: 42,
+ height: 42,
+ borderRadius: 21,
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexShrink: 0,
+ },
+ notifContent: { marginLeft: 10 },
+ notifMarkReadText: { fontSize: 11, color: '#3B82F6', fontWeight: '600' },
+});
+
+export default NotificationStyles;