upd: caching data

Deskripsi:
- update caching pada fitur utama -yg fitur divisi belom
This commit is contained in:
2026-04-20 14:23:14 +08:00
parent 772551a917
commit ccf8ee1caf
27 changed files with 767 additions and 766 deletions

View File

@@ -29,6 +29,7 @@ export default function ViewLogin({ onValidate }: Props) {
setLoadingLogin(true)
const response = await apiCheckPhoneLogin({ phone: `62${phone}` })
if (response.success) {
console.log({ response })
if (response.isWithoutOTP) {
const encrypted = await encryptToken(response.id)
signIn(encrypted)

View File

@@ -33,7 +33,7 @@ export default function HeaderRightDivisionList() {
}}
/>
{
(entityUser.role == "userRole" || entityUser.role == "developer") &&
(entityUser.role == "supadmin" || entityUser.role == "developer") &&
<MenuItemRow
icon={<AntDesign name="filter" color={colors.text} size={25} />}
title="Filter"

View File

@@ -5,7 +5,8 @@ import { setEntities } from "@/lib/bannerSlice";
import { setEntityUser } from "@/lib/userSlice";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { AntDesign, Feather, FontAwesome5, Ionicons, MaterialCommunityIcons, MaterialIcons, } from "@expo/vector-icons";
import { Feather, Ionicons } from "@expo/vector-icons";
import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import React, { useEffect } from "react";
import { Dimensions, Image, View } from "react-native";
@@ -23,37 +24,44 @@ export default function CaraouselHome2({ refreshing }: { refreshing: boolean })
const progress = useSharedValue<number>(0);
const dispatch = useDispatch()
const entities = useSelector((state: any) => state.banner)
const entityUser = useSelector((state: any) => state.user)
async function handleBannerView() {
const hasil = await decryptToken(String(token?.current))
apiGetBanner({ user: hasil }).then((data) => {
if (data.data.length > 0) {
dispatch(setEntities(data.data))
} else {
dispatch(setEntities([]))
}
})
}
// Query for Banners
const { data: banners } = useQuery({
queryKey: ['banners'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const data = await apiGetBanner({ user: hasil })
return data.data || []
},
enabled: !!token?.current,
staleTime: 0,
})
async function handleUser() {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetProfile({ id: hasil })
dispatch(setEntityUser({ role: response.data.idUserRole, admin: false }))
}
// Query for Profile (Role Check)
const { data: profile } = useQuery({
queryKey: ['profile'], // Shares same key as Home.tsx
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const data = await apiGetProfile({ id: hasil })
return data.data
},
enabled: !!token?.current,
staleTime: 0,
})
// Sync Banners to Redux
useEffect(() => {
if (refreshing)
handleBannerView()
}, [refreshing]);
if (banners) {
dispatch(setEntities(banners))
}
}, [banners, dispatch])
// Sync User Role to Redux
useEffect(() => {
handleBannerView()
}, [dispatch]);
useEffect(() => {
handleUser()
}, []);
if (profile) {
dispatch(setEntityUser({ role: profile.idUserRole, admin: false }))
}
}, [profile, dispatch])
return (
<View

View File

@@ -2,7 +2,8 @@ import Styles from "@/constants/Styles";
import { apiGetDataHome } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { Dimensions, View } from "react-native";
import { BarChart } from "react-native-gifted-charts";
import Skeleton from "../skeleton";
@@ -15,64 +16,44 @@ type Props = {
}[]
export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }) {
const [loading, setLoading] = useState(true)
const { decryptToken, token } = useAuthSession()
const { colors } = useTheme();
const [data, setData] = useState<Props>([])
const [maxValue, setMaxValue] = useState(5)
const [chartKey, setChartKey] = useState(0)
const barData = [
{ value: 23, label: 'Gambar', frontColor: '#fac858' },
{ value: 12, label: 'Dokumen', frontColor: '#92cc76' },
];
const width = Dimensions.get("window").width;
async function handleData(loading: boolean) {
try {
setLoading(loading)
// TanStack Query for Document Chart data
const { data: chartData = [], isLoading } = useQuery({
queryKey: ['homeData', 'dokumen'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "dokumen", user: hasil })
const maxVal = response.data.reduce((max: number, obj: { value: number; }) => Math.max(max, Number(obj.value)), 0);
const roundUp = maxVal > 0 ? Math.ceil(maxVal / 10) * 10 : 10;
setMaxValue(roundUp)
const convertedArray = response.data.map((item: { color: any; label: any; value: any; }) => ({
return response.data.map((item: { color: any; label: any; value: any; }) => ({
frontColor: item.color,
label: item.label,
value: Number(item.value)
}));
setData(convertedArray)
setChartKey((prev: number) => prev + 1)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
})) as Props
},
enabled: !!token?.current,
staleTime: 0,
})
useEffect(() => {
if (refreshing) {
handleData(false)
}
}, [refreshing]);
useEffect(() => {
handleData(true)
}, []);
// Derived state for maxValue
const maxValue = useMemo(() => {
const maxVal = chartData.reduce((max: number, obj: { value: number; }) => Math.max(max, obj.value), 0);
return maxVal > 0 ? Math.ceil(maxVal / 10) * 10 : 10;
}, [chartData]);
return (
<View style={[Styles.wrapPaper, Styles.contentItemCenter, Styles.mb15, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
<Text style={[Styles.textSubtitle, Styles.mv15]}>JUMLAH DOKUMEN</Text>
{
loading ? <Skeleton width={100} height={200} borderRadius={10} widthType="percent" />
isLoading ? <Skeleton width={100} height={200} borderRadius={10} widthType="percent" />
:
<BarChart
key={chartKey}
showFractionalValues={false}
showYAxisIndices
noOfSections={4}
maxValue={maxValue}
data={data}
data={chartData}
isAnimated
width={width - 140}
barWidth={width * 0.25}
@@ -92,7 +73,6 @@ export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }
}}
/>
}
</View>
)
}

View File

@@ -2,7 +2,7 @@ import Styles from "@/constants/Styles";
import { apiGetDataHome } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { View } from "react-native";
import { PieChart } from "react-native-gifted-charts";
import Skeleton from "../skeleton";
@@ -17,45 +17,32 @@ type Props = {
export default function ChartProgresHome({ refreshing }: { refreshing: boolean }) {
const { decryptToken, token } = useAuthSession()
const { colors } = useTheme();
const [data, setData] = useState<Props>([])
const [loading, setLoading] = useState(true)
async function handleData(loading: boolean) {
try {
setLoading(loading)
// TanStack Query for Progress Chart data
const { data: chartData = [], isLoading } = useQuery({
queryKey: ['homeData', 'progress'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "progress", user: hasil })
const convertedArray = response.data.map((item: { color: any; text: any; value: any; }) => ({
return response.data.map((item: { color: any; text: any; value: any; }) => ({
color: item.color,
text: item.text,
value: Number(item.value)
}));
setData(convertedArray)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (refreshing)
handleData(false)
}, [refreshing]);
useEffect(() => {
handleData(true)
}, []);
})) as Props
},
enabled: !!token?.current,
staleTime: 0,
})
return (
<View style={[Styles.wrapPaper, Styles.contentItemCenter, Styles.mb15, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
<Text style={[Styles.textSubtitle, Styles.mv15]}>PROGRES KEGIATAN</Text>
{
loading ? <Skeleton width={100} height={200} borderRadius={10} widthType="percent" />
isLoading ? <Skeleton width={100} height={200} borderRadius={10} widthType="percent" />
:
<>
<PieChart
data={data}
data={chartData}
showText
showValuesAsTooltipText
textColor={'black'}

View File

@@ -1,13 +1,13 @@
import Styles from "@/constants/Styles";
import { apiGetDataHome } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import { useEffect, useState } from "react";
import { View } from "react-native";
import DiscussionItem from "../discussionItem";
import Skeleton from "../skeleton";
import Text from "../Text";
import { useTheme } from "@/providers/ThemeProvider";
type Props = {
id: string
@@ -20,46 +20,33 @@ type Props = {
export default function DisccussionHome({ refreshing }: { refreshing: boolean }) {
const { decryptToken, token } = useAuthSession()
const [data, setData] = useState<Props[]>([])
const [loading, setLoading] = useState(true)
const { colors } = useTheme();
async function handleData(loading: boolean) {
try {
setLoading(loading)
// TanStack Query for Discussion data
const { data: homeDiscussions = [], isLoading } = useQuery({
queryKey: ['homeData', 'discussion'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "discussion", user: hasil })
setData(response.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (refreshing)
handleData(false)
}, [refreshing]);
useEffect(() => {
handleData(true)
}, []);
return response.data as Props[]
},
enabled: !!token?.current,
staleTime: 0,
})
return (
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mv10]}>Diskusi</Text>
<View style={[Styles.wrapPaper, { backgroundColor: colors.card, borderColor: colors.icon + '20' }, Styles.p0]}>
{
loading ?
isLoading ?
<>
<Skeleton width={100} height={70} borderRadius={10} widthType="percent" />
<Skeleton width={100} height={70} borderRadius={10} widthType="percent" />
</>
:
data.length > 0 ?
data.map((item, index) => {
homeDiscussions.length > 0 ?
homeDiscussions.map((item: Props, index: number) => {
return (
<DiscussionItem key={index} title={item.desc} user={item.user} date={item.date} onPress={() => { router.push(`/division/${item.idDivision}/discussion/${item.id}`) }} />
)

View File

@@ -3,8 +3,9 @@ import { apiGetDataHome } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { Feather } from "@expo/vector-icons";
import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import React, { useEffect, useState } from "react";
import React from "react";
import { Dimensions, Pressable, View } from "react-native";
import { ICarouselInstance } from "react-native-reanimated-carousel";
import Skeleton from "../skeleton";
@@ -21,45 +22,31 @@ export default function DivisionHome({ refreshing }: { refreshing: boolean }) {
const { colors } = useTheme();
const ref = React.useRef<ICarouselInstance>(null)
const width = Dimensions.get("window").width
const [data, setData] = useState<Props[]>([])
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 2 }, (_, index) => index)
async function handleData(loading: boolean) {
try {
setLoading(loading)
// TanStack Query for Division data
const { data: homeDivisions = [], isLoading } = useQuery({
queryKey: ['homeData', 'division'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "division", user: hasil })
setData(response.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (refreshing)
handleData(false)
}, [refreshing]);
useEffect(() => {
handleData(true)
}, []);
return response.data as Props[]
},
enabled: !!token?.current,
staleTime: 0,
})
return (
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mb10]}>Divisi Teraktif</Text>
{
loading ?
isLoading ?
arrSkeleton.map((item, index) => (
<Skeleton key={index} width={100} height={80} borderRadius={10} widthType="percent" />
))
:
data.length > 0 ?
data.map((item, index) => (
homeDivisions.length > 0 ?
homeDivisions.map((item, index) => (
<Pressable style={[Styles.wrapPaper, Styles.mb05, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]} key={index} onPress={() => { router.push(`/division/${item.id}`) }}>
<View style={[Styles.rowSpaceBetween, { alignItems: 'center' }]}>
<View>

View File

@@ -2,8 +2,8 @@ import Styles from "@/constants/Styles";
import { apiGetDataHome } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import { useEffect, useState } from "react";
import { View } from "react-native";
import EventItem from "../eventItem";
import Skeleton from "../skeleton";
@@ -26,30 +26,18 @@ type Props = {
export default function EventHome({ refreshing }: { refreshing: boolean }) {
const { decryptToken, token } = useAuthSession()
const { colors } = useTheme();
const [data, setData] = useState<Props[]>([])
const [loading, setLoading] = useState(true)
async function handleData(loading: boolean) {
try {
setLoading(loading)
// TanStack Query for Event data
const { data: homeEvents = [], isLoading } = useQuery({
queryKey: ['homeData', 'event'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "event", user: hasil })
setData(response.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (refreshing)
handleData(false)
}, [refreshing]);
useEffect(() => {
handleData(true)
}, []);
return response.data as Props[]
},
enabled: !!token?.current,
staleTime: 0,
})
return (
<View style={[Styles.mb15]}>
@@ -57,14 +45,14 @@ export default function EventHome({ refreshing }: { refreshing: boolean }) {
<View style={[Styles.wrapPaper, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
{
loading ?
isLoading ?
<>
<Skeleton width={100} height={70} borderRadius={10} widthType="percent" />
<Skeleton width={100} height={70} borderRadius={10} widthType="percent" />
</>
:
data.length > 0 ?
data.map((item, index) => {
homeEvents.length > 0 ?
homeEvents.map((item: Props, index: number) => {
return (
<EventItem key={index} category={index % 2 == 0 ? 'purple' : 'orange'} onPress={() => { router.push(`/division/${item.idDivision}/calendar/${item.id}`) }} title={item.title} user={item.user_name} jamAwal={item.timeStart} jamAkhir={item.timeEnd} />
)

View File

@@ -2,8 +2,9 @@ import Styles from "@/constants/Styles";
import { apiGetDataHome } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import React, { useEffect, useState } from "react";
import React from "react";
import { Dimensions, View } from "react-native";
import Carousel, { ICarouselInstance } from "react-native-reanimated-carousel";
import LabelStatus from "../labelStatus";
@@ -25,45 +26,33 @@ export default function ProjectHome({ refreshing }: { refreshing: boolean }) {
const { decryptToken, token } = useAuthSession()
const ref = React.useRef<ICarouselInstance>(null);
const width = Dimensions.get("window").width;
const [data, setData] = useState<Props[]>([])
const [loading, setLoading] = useState(true)
const { colors } = useTheme();
async function handleData(loading: boolean) {
try {
setLoading(loading)
// TanStack Query for Projects data
const { data: homeProjects = [], isLoading } = useQuery({
queryKey: ['homeData', 'kegiatan'],
queryFn: async () => {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "kegiatan", user: hasil })
setData(response.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (refreshing)
handleData(false)
}, [refreshing]);
useEffect(() => {
handleData(true)
}, []);
return response.data as Props[]
},
enabled: !!token?.current,
staleTime: 0,
})
return (
<View style={[Styles.mb05]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mb10]}>Kegiatan Terupdate</Text>
{
loading ? (<Skeleton width={100} height={150} borderRadius={10} widthType="percent" />)
isLoading ? (<Skeleton width={100} height={150} borderRadius={10} widthType="percent" />)
:
data.length > 0 ?
homeProjects.length > 0 ?
<Carousel
ref={ref}
style={{ width: "100%" }}
width={width * 0.8}
height={220}
data={data}
data={homeProjects}
loop={false}
autoPlay={false}
autoPlayReverse={false}
@@ -71,24 +60,24 @@ export default function ProjectHome({ refreshing }: { refreshing: boolean }) {
snapEnabled={true}
vertical={false}
renderItem={({ index }) => (
<PaperGridContent titleTail={1} content="carousel" onPress={() => { router.push(`/project/${data[index].id}`) }} title={data[index].title} headerColor="primary">
<ProgressBar value={data[index].progress} category="carousel" />
<PaperGridContent titleTail={1} content="carousel" onPress={() => { router.push(`/project/${homeProjects[index].id}`) }} title={homeProjects[index].title} headerColor="primary">
<ProgressBar value={homeProjects[index].progress} category="carousel" />
<View style={[Styles.rowSpaceBetween]}>
<Text style={[Styles.textDefault, { color: colors.dimmed }]}>{data[index].createdAt}</Text>
<Text style={[Styles.textDefault, { color: colors.dimmed }]}>{homeProjects[index].createdAt}</Text>
<LabelStatus
size="default"
category={
data[index].status === 0 ? 'secondary' :
data[index].status === 1 ? 'warning' :
data[index].status === 2 ? 'success' :
data[index].status === 3 ? 'error' :
homeProjects[index].status === 0 ? 'secondary' :
homeProjects[index].status === 1 ? 'warning' :
homeProjects[index].status === 2 ? 'success' :
homeProjects[index].status === 3 ? 'error' :
'secondary'
}
text={
data[index].status === 0 ? 'SEGERA' :
data[index].status === 1 ? 'DIKERJAKAN' :
data[index].status === 2 ? 'SELESAI' :
data[index].status === 3 ? 'DIBATALKAN' :
homeProjects[index].status === 0 ? 'SEGERA' :
homeProjects[index].status === 1 ? 'DIKERJAKAN' :
homeProjects[index].status === 2 ? 'SELESAI' :
homeProjects[index].status === 3 ? 'DIBATALKAN' :
'SEGERA'
}
/>