feat: redesign halaman detail user dan profile dengan tampilan modern
- Pindahkan badge APPROVER & AKTIF ke dalam header gradient - Ganti card berlatar menjadi list dengan border bottom saja - Gunakan icon colors.icon agar terlihat pada tema gelap - Tambahkan class baru di Styles.ts: memberAvatarRing, memberBadgeRow, memberBadgeApprover, memberBadgePill, memberInfoRow, memberInfoIcon, memberInfoContent, cWhiteDimmed, pv14, mb08 - Terapkan design yang sama pada halaman profile
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import AppHeader from "@/components/AppHeader";
|
import AppHeader from "@/components/AppHeader";
|
||||||
import ImageUser from "@/components/imageNew";
|
import ImageUser from "@/components/imageNew";
|
||||||
import ItemDetailMember from "@/components/itemDetailMember";
|
|
||||||
import LabelStatus from "@/components/labelStatus";
|
import LabelStatus from "@/components/labelStatus";
|
||||||
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
|
import HeaderRightMemberDetail from "@/components/member/headerMemberDetail";
|
||||||
import Skeleton from "@/components/skeleton";
|
import Skeleton from "@/components/skeleton";
|
||||||
@@ -11,6 +10,7 @@ import { valueRoleUser } from "@/constants/RoleUser";
|
|||||||
import Styles from "@/constants/Styles";
|
import Styles from "@/constants/Styles";
|
||||||
import { apiGetProfile } from "@/lib/api";
|
import { apiGetProfile } from "@/lib/api";
|
||||||
import { useTheme } from "@/providers/ThemeProvider";
|
import { useTheme } from "@/providers/ThemeProvider";
|
||||||
|
import { AntDesign, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||||
import { LinearGradient } from "expo-linear-gradient";
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
import { router, Stack, useLocalSearchParams } from "expo-router";
|
import { router, Stack, useLocalSearchParams } from "expo-router";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
@@ -110,60 +110,71 @@ export default function MemberDetail() {
|
|||||||
colors={[colors.header, colors.homeGradient]}
|
colors={[colors.header, colors.homeGradient]}
|
||||||
style={[Styles.wrapHeadViewMember]}
|
style={[Styles.wrapHeadViewMember]}
|
||||||
>
|
>
|
||||||
{
|
{loading ? (
|
||||||
loading ?
|
<>
|
||||||
<>
|
<Skeleton width={100} height={100} borderRadius={100} />
|
||||||
<Skeleton width={100} height={100} borderRadius={100} />
|
<Skeleton width={200} height={10} borderRadius={5} />
|
||||||
<Skeleton width={200} height={10} borderRadius={5} />
|
<Skeleton width={150} height={10} borderRadius={5} />
|
||||||
<Skeleton width={150} height={10} borderRadius={5} />
|
</>
|
||||||
</>
|
) : (
|
||||||
:
|
<>
|
||||||
<>
|
<Pressable onPress={() => setPreview(true)}>
|
||||||
<Pressable onPress={() => setPreview(true)}>
|
<View style={[Styles.memberAvatarRing]}>
|
||||||
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" onError={setErrorImg} />
|
<ImageUser src={`${ConstEnv.url_storage}/files/${data?.img}`} size="lg" onError={setErrorImg} />
|
||||||
</Pressable>
|
</View>
|
||||||
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10, Styles.textCenter]}>{data?.name}</Text>
|
</Pressable>
|
||||||
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{data?.role}</Text>
|
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10, Styles.textCenter]}>{data?.name}</Text>
|
||||||
</>
|
<Text style={[Styles.textMediumNormal, Styles.cWhiteDimmed]}>{data?.role}</Text>
|
||||||
|
<View style={[Styles.memberBadgeRow]}>
|
||||||
}
|
{data?.isApprover && (
|
||||||
|
<View style={[Styles.memberBadgeApprover]}>
|
||||||
|
<Text style={[Styles.textSmallSemiBold, Styles.cWhite]}>APPROVER</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View style={[Styles.memberBadgePill, { backgroundColor: data?.isActive ? colors.success : colors.error }]}>
|
||||||
|
<Text style={[Styles.textSmallSemiBold, Styles.cWhite]}>{data?.isActive ? 'AKTIF' : 'TIDAK AKTIF'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
<View style={[Styles.p15]}>
|
|
||||||
<View style={[Styles.rowSpaceBetween]}>
|
|
||||||
<Text style={[Styles.textDefaultSemiBold, { color: colors.text }]}>Informasi</Text>
|
|
||||||
<View style={{ flexDirection: 'row', gap: 6 }}>
|
|
||||||
{data?.isApprover && (
|
|
||||||
<LabelStatus
|
|
||||||
size="small"
|
|
||||||
category="primary"
|
|
||||||
text="APPROVER"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<LabelStatus
|
|
||||||
size="small"
|
|
||||||
category={data?.isActive ? 'success' : 'error'}
|
|
||||||
text={data?.isActive ? 'AKTIF' : 'TIDAK AKTIF'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{
|
|
||||||
loading ?
|
|
||||||
arrSkeleton.map((item, index) => {
|
|
||||||
return (
|
|
||||||
<Skeleton key={index} width={100} widthType="percent" height={25} borderRadius={5} />
|
|
||||||
)
|
|
||||||
})
|
|
||||||
:
|
|
||||||
<>
|
|
||||||
<ItemDetailMember category="nik" value={data?.nik} />
|
|
||||||
<ItemDetailMember category="group" value={data?.group} />
|
|
||||||
<ItemDetailMember category="position" value={data?.position} />
|
|
||||||
<ItemDetailMember category="phone" value={`+62${data?.phone}`} />
|
|
||||||
<ItemDetailMember category="email" value={data?.email} />
|
|
||||||
<ItemDetailMember category="gender" value={data?.gender == "F" ? "Perempuan" : "Laki-Laki"} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<View style={[Styles.p15]}>
|
||||||
|
<Text style={[Styles.textDefaultSemiBold, Styles.mb08, { color: colors.dimmed }]}>Informasi</Text>
|
||||||
|
<View>
|
||||||
|
{loading ? (
|
||||||
|
arrSkeleton.map((_, index) => (
|
||||||
|
<View key={index} style={[Styles.pv14, { borderBottomWidth: index < arrSkeleton.length - 1 ? 1 : 0, borderBottomColor: `${colors.dimmed}30` }]}>
|
||||||
|
<Skeleton width={80} height={8} borderRadius={4} />
|
||||||
|
<View style={[Styles.mt05]}>
|
||||||
|
<Skeleton width={60} widthType="percent" height={10} borderRadius={4} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
[
|
||||||
|
{ icon: <MaterialCommunityIcons name="card-account-details" size={20} color={colors.icon} />, label: 'NIK', value: data?.nik },
|
||||||
|
{ icon: <MaterialCommunityIcons name="office-building-outline" size={20} color={colors.icon} />, label: 'Lembaga Desa', value: data?.group },
|
||||||
|
{ icon: <MaterialCommunityIcons name="account-tie" size={20} color={colors.icon} />, label: 'Jabatan', value: data?.position },
|
||||||
|
{ icon: <MaterialIcons name="phone" size={20} color={colors.icon} />, label: 'No Telepon', value: `+62${data?.phone}` },
|
||||||
|
{ icon: <MaterialIcons name="email" size={20} color={colors.icon} />, label: 'Email', value: data?.email },
|
||||||
|
{ icon: <MaterialCommunityIcons name="gender-male-female" size={20} color={colors.icon} />, label: 'Jenis Kelamin', value: data?.gender == "F" ? "Perempuan" : "Laki-Laki" },
|
||||||
|
].map((item, index, arr) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={[Styles.memberInfoRow, { borderBottomWidth: index < arr.length - 1 ? 1 : 0, borderBottomColor: `${colors.dimmed}30` }]}
|
||||||
|
>
|
||||||
|
<View style={[Styles.memberInfoIcon]}>
|
||||||
|
{item.icon}
|
||||||
|
</View>
|
||||||
|
<View style={[Styles.memberInfoContent]}>
|
||||||
|
<Text style={[Styles.textInformation, { color: colors.dimmed }]}>{item.label}</Text>
|
||||||
|
<Text style={[Styles.textDefault, Styles.mt02, { color: colors.text }]} numberOfLines={1}>{item.value ?? '-'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import AppHeader from "@/components/AppHeader";
|
import AppHeader from "@/components/AppHeader";
|
||||||
import { ButtonHeader } from "@/components/buttonHeader";
|
import { ButtonHeader } from "@/components/buttonHeader";
|
||||||
import ItemDetailMember from "@/components/itemDetailMember";
|
|
||||||
import Text from "@/components/Text";
|
import Text from "@/components/Text";
|
||||||
import { assetUserImage } from "@/constants/AssetsError";
|
import { assetUserImage } from "@/constants/AssetsError";
|
||||||
import { ConstEnv } from "@/constants/ConstEnv";
|
import { ConstEnv } from "@/constants/ConstEnv";
|
||||||
@@ -9,7 +8,7 @@ import { apiGetProfile } from "@/lib/api";
|
|||||||
import { setEntities } from "@/lib/entitiesSlice";
|
import { setEntities } from "@/lib/entitiesSlice";
|
||||||
import { useAuthSession } from "@/providers/AuthProvider";
|
import { useAuthSession } from "@/providers/AuthProvider";
|
||||||
import { useTheme } from "@/providers/ThemeProvider";
|
import { useTheme } from "@/providers/ThemeProvider";
|
||||||
import { Feather } from "@expo/vector-icons";
|
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||||
import { LinearGradient } from "expo-linear-gradient";
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
import { router, Stack } from "expo-router";
|
import { router, Stack } from "expo-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -42,6 +41,15 @@ export default function Profile() {
|
|||||||
setRefreshing(false)
|
setRefreshing(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const infoRows = [
|
||||||
|
{ icon: <MaterialCommunityIcons name="card-account-details" size={20} color={colors.icon} />, label: 'NIK', value: entities.nik },
|
||||||
|
{ icon: <MaterialCommunityIcons name="office-building-outline" size={20} color={colors.icon} />, label: 'Lembaga Desa', value: entities.group },
|
||||||
|
{ icon: <MaterialCommunityIcons name="account-tie" size={20} color={colors.icon} />, label: 'Jabatan', value: entities.position },
|
||||||
|
{ icon: <MaterialIcons name="phone" size={20} color={colors.icon} />, label: 'No Telepon', value: `0${entities.phone}` },
|
||||||
|
{ icon: <MaterialIcons name="email" size={20} color={colors.icon} />, label: 'Email', value: entities.email },
|
||||||
|
{ icon: <MaterialCommunityIcons name="gender-male-female" size={20} color={colors.icon} />, label: 'Jenis Kelamin', value: entities.gender == "F" ? 'Perempuan' : 'Laki-laki' },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}>
|
<SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@@ -56,9 +64,7 @@ export default function Profile() {
|
|||||||
right={
|
right={
|
||||||
<ButtonHeader
|
<ButtonHeader
|
||||||
item={<Feather name="settings" size={20} color="white" />}
|
item={<Feather name="settings" size={20} color="white" />}
|
||||||
onPress={() => {
|
onPress={() => router.push('/setting')}
|
||||||
router.push('/setting')
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -75,32 +81,47 @@ export default function Profile() {
|
|||||||
}
|
}
|
||||||
style={[Styles.h100, { backgroundColor: colors.background }]}
|
style={[Styles.h100, { backgroundColor: colors.background }]}
|
||||||
>
|
>
|
||||||
<View style={[Styles.flexColumn]}>
|
<LinearGradient
|
||||||
<LinearGradient
|
colors={[colors.header, colors.homeGradient]}
|
||||||
colors={[colors.header, colors.homeGradient]}
|
style={[Styles.wrapHeadViewMember]}
|
||||||
style={[Styles.wrapHeadViewMember]}
|
>
|
||||||
>
|
<Pressable onPress={() => setPreview(true)}>
|
||||||
<Pressable onPress={() => setPreview(true)}>
|
<View style={[Styles.memberAvatarRing]}>
|
||||||
<Image
|
<Image
|
||||||
source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }}
|
source={error ? require("../../assets/images/user.jpg") : { uri: `${ConstEnv.url_storage}/files/${entities.img}` }}
|
||||||
onError={() => { setError(true) }}
|
onError={() => setError(true)}
|
||||||
style={[Styles.userProfileBig]}
|
style={[Styles.userProfileBig]}
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
|
||||||
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10]}>{entities.name}</Text>
|
|
||||||
<Text style={[Styles.textMediumNormal, Styles.cWhite]}>{entities.role}</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
<View style={[Styles.p15]}>
|
|
||||||
<View style={[Styles.rowSpaceBetween]}>
|
|
||||||
<Text style={[Styles.textDefaultSemiBold, { color: colors.text }]}>Informasi</Text>
|
|
||||||
</View>
|
</View>
|
||||||
{/* Note: ItemDetailMember might need updates to support dynamic colors if it uses default text colors */}
|
</Pressable>
|
||||||
<ItemDetailMember category="nik" value={entities.nik} />
|
<Text style={[Styles.textSubtitle, Styles.cWhite, Styles.mt10, Styles.textCenter]}>{entities.name}</Text>
|
||||||
<ItemDetailMember category="group" value={entities.group} />
|
<Text style={[Styles.textMediumNormal, Styles.cWhiteDimmed]}>{entities.role}</Text>
|
||||||
<ItemDetailMember category="position" value={entities.position} />
|
{entities.isApprover && (
|
||||||
<ItemDetailMember category="phone" value={`0${entities.phone}`} />
|
<View style={[Styles.memberBadgeRow, { justifyContent: 'center' }]}>
|
||||||
<ItemDetailMember category="email" value={entities.email} />
|
<View style={[Styles.memberBadgeApprover]}>
|
||||||
<ItemDetailMember category="gender" value={entities.gender == "F" ? 'Perempuan' : 'Laki-laki'} />
|
<Text style={[Styles.textSmallSemiBold, Styles.cWhite]}>APPROVER</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</LinearGradient>
|
||||||
|
|
||||||
|
<View style={[Styles.p15]}>
|
||||||
|
<Text style={[Styles.textDefaultSemiBold, Styles.mb08, { color: colors.dimmed }]}>Informasi</Text>
|
||||||
|
<View>
|
||||||
|
{infoRows.map((item, index, arr) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={[Styles.memberInfoRow, { borderBottomWidth: index < arr.length - 1 ? 1 : 0, borderBottomColor: `${colors.dimmed}30` }]}
|
||||||
|
>
|
||||||
|
<View style={[Styles.memberInfoIcon]}>
|
||||||
|
{item.icon}
|
||||||
|
</View>
|
||||||
|
<View style={[Styles.memberInfoContent]}>
|
||||||
|
<Text style={[Styles.textInformation, { color: colors.dimmed }]}>{item.label}</Text>
|
||||||
|
<Text style={[Styles.textDefault, Styles.mt02, { color: colors.text }]} numberOfLines={1}>{item.value ?? '-'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -114,4 +135,4 @@ export default function Profile() {
|
|||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1068,6 +1068,51 @@ const Styles = StyleSheet.create({
|
|||||||
color: 'white',
|
color: 'white',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
pv14: {
|
||||||
|
paddingVertical: 14,
|
||||||
|
},
|
||||||
|
mb08: {
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
cWhiteDimmed: {
|
||||||
|
color: 'rgba(255,255,255,0.7)',
|
||||||
|
},
|
||||||
|
memberAvatarRing: {
|
||||||
|
borderWidth: 3,
|
||||||
|
borderColor: 'rgba(255,255,255,0.4)',
|
||||||
|
borderRadius: 100,
|
||||||
|
},
|
||||||
|
memberBadgeRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 8,
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
memberBadgeApprover: {
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 20,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.6)',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.15)',
|
||||||
|
},
|
||||||
|
memberBadgePill: {
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 20,
|
||||||
|
},
|
||||||
|
memberInfoRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: 14,
|
||||||
|
},
|
||||||
|
memberInfoIcon: {
|
||||||
|
width: 36,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
memberInfoContent: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Styles;
|
export default Styles;
|
||||||
Reference in New Issue
Block a user