266 lines
9.9 KiB
TypeScript
266 lines
9.9 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
View,
|
|
ScrollView,
|
|
Pressable,
|
|
ActivityIndicator,
|
|
Image,
|
|
} from "react-native";
|
|
import { useSirouRouter } from "@sirou/react-native";
|
|
import { useLocalSearchParams } from "expo-router";
|
|
import { AppRoutes } from "@/lib/routes";
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { StandardHeader } from "@/components/StandardHeader";
|
|
import { Text } from "@/components/ui/text";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { api } from "@/lib/api";
|
|
import { useColorScheme } from "nativewind";
|
|
import {
|
|
Mail,
|
|
Phone,
|
|
ShieldCheck,
|
|
Calendar,
|
|
ChevronRight,
|
|
} from "@/lib/icons";
|
|
|
|
export default function TeamMemberDetailsScreen() {
|
|
const nav = useSirouRouter<AppRoutes>();
|
|
const { id } = useLocalSearchParams<{ id: string }>();
|
|
const { colorScheme } = useColorScheme();
|
|
const isDark = colorScheme === "dark";
|
|
|
|
const [member, setMember] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const res = await api.team.getById({ params: { id } });
|
|
setMember(res?.data ?? res);
|
|
} catch (err) {
|
|
console.error("[TeamMemberDetails] Error:", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
load();
|
|
}, [id]);
|
|
|
|
const iconCol = isDark ? "#94a3b8" : "#64748b";
|
|
|
|
if (loading) {
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<StandardHeader title="Worker Details" showBack />
|
|
<View className="flex-1 items-center justify-center">
|
|
<ActivityIndicator size="large" color="#E46212" />
|
|
</View>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|
|
|
|
if (!member) {
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<StandardHeader title="Worker Details" showBack />
|
|
<View className="flex-1 items-center justify-center px-5">
|
|
<Text className="text-muted-foreground">Worker not found</Text>
|
|
</View>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<StandardHeader title="Worker Details" showBack />
|
|
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={{ padding: 16, paddingBottom: 40 }}
|
|
>
|
|
{/* Avatar + Name */}
|
|
<View className="items-center mb-8">
|
|
{member.avatar ? (
|
|
<View className="h-20 w-20 rounded-full overflow-hidden bg-muted mb-3">
|
|
<Image
|
|
source={{ uri: member.avatar }}
|
|
className="h-full w-full"
|
|
resizeMode="cover"
|
|
/>
|
|
</View>
|
|
) : (
|
|
<View className="h-20 w-20 rounded-full bg-secondary items-center justify-center mb-3">
|
|
<Text className="text-primary font-sans-bold text-3xl">
|
|
{member.firstName?.[0]}
|
|
{member.lastName?.[0]}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
<Text className="text-foreground text-xl font-sans-bold text-center">
|
|
{member.firstName} {member.lastName}
|
|
</Text>
|
|
<View className="mt-2 flex-row items-center">
|
|
<View className="px-3 py-1 rounded-[4px] bg-slate-500/10">
|
|
<Text className="text-slate-600 text-[9px] font-sans-bold uppercase tracking-widest">
|
|
{member.role?.replace("_", " ") || "MEMBER"}
|
|
</Text>
|
|
</View>
|
|
{member.status && (
|
|
<View className={`ml-2 px-3 py-1 rounded-[4px] ${
|
|
member.status === "ACTIVE" ? "bg-emerald-500/10" : "bg-slate-500/10"
|
|
}`}>
|
|
<Text className={`text-[9px] font-sans-bold uppercase tracking-widest ${
|
|
member.status === "ACTIVE" ? "text-emerald-600" : "text-slate-600"
|
|
}`}>
|
|
{member.status}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Contact Info */}
|
|
<Card className="mb-4">
|
|
<CardContent className="py-4">
|
|
<Text className="text-[11px] font-sans-bold uppercase tracking-widest text-muted-foreground mb-4">
|
|
Contact Information
|
|
</Text>
|
|
<View className="gap-4">
|
|
<View className="flex-row items-center">
|
|
<View className="h-8 w-8 rounded-lg bg-muted items-center justify-center mr-3">
|
|
<Mail size={15} color={iconCol} />
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[10px] font-sans-medium text-muted-foreground">
|
|
Email
|
|
</Text>
|
|
<Text className="text-foreground text-sm font-sans-medium mt-0.5">
|
|
{member.email || "—"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<View className="flex-row items-center">
|
|
<View className="h-8 w-8 rounded-lg bg-muted items-center justify-center mr-3">
|
|
<Phone size={15} color={iconCol} />
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[10px] font-sans-medium text-muted-foreground">
|
|
Phone
|
|
</Text>
|
|
<Text className="text-foreground text-sm font-sans-medium mt-0.5">
|
|
{member.phone || "—"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<View className="flex-row items-center">
|
|
<View className="h-8 w-8 rounded-lg bg-muted items-center justify-center mr-3">
|
|
<ShieldCheck size={15} color={iconCol} />
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[10px] font-sans-medium text-muted-foreground">
|
|
Role
|
|
</Text>
|
|
<Text className="text-foreground text-sm font-sans-medium mt-0.5">
|
|
{member.role?.replace("_", " ") || "MEMBER"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Activity */}
|
|
<Card className="mb-4">
|
|
<CardContent className="py-4">
|
|
<Text className="text-[11px] font-sans-bold uppercase tracking-widest text-muted-foreground mb-4">
|
|
Recent Activity
|
|
</Text>
|
|
{member.activities && member.activities.length > 0 ? (
|
|
<View className="gap-3">
|
|
{member.activities.map((act: any, i: number) => (
|
|
<View
|
|
key={act.id || i}
|
|
className="flex-row items-start"
|
|
>
|
|
<View className="h-2 w-2 rounded-full bg-primary mt-1.5 mr-3" />
|
|
<View className="flex-1">
|
|
<Text className="text-foreground text-sm font-sans-medium">
|
|
{act.description || act.action}
|
|
</Text>
|
|
<Text className="text-muted-foreground text-[10px] font-sans-medium mt-0.5">
|
|
{act.createdAt
|
|
? new Date(act.createdAt).toLocaleDateString("en-GB", {
|
|
day: "numeric",
|
|
month: "short",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})
|
|
: ""}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
) : (
|
|
<Text className="text-muted-foreground text-sm font-sans-medium">
|
|
No recent activity
|
|
</Text>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Account Info */}
|
|
<Card>
|
|
<CardContent className="py-4">
|
|
<Text className="text-[11px] font-sans-bold uppercase tracking-widest text-muted-foreground mb-4">
|
|
Account Information
|
|
</Text>
|
|
<View className="gap-3">
|
|
<View className="flex-row items-center">
|
|
<View className="h-8 w-8 rounded-lg bg-muted items-center justify-center mr-3">
|
|
<Calendar size={15} color={iconCol} />
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[10px] font-sans-medium text-muted-foreground">
|
|
Created
|
|
</Text>
|
|
<Text className="text-foreground text-sm font-sans-medium mt-0.5">
|
|
{member.createdAt
|
|
? new Date(member.createdAt).toLocaleDateString("en-GB", {
|
|
day: "numeric",
|
|
month: "short",
|
|
year: "numeric",
|
|
})
|
|
: "—"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<View className="flex-row items-center">
|
|
<View className="h-8 w-8 rounded-lg bg-muted items-center justify-center mr-3">
|
|
<Calendar size={15} color={iconCol} />
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[10px] font-sans-medium text-muted-foreground">
|
|
Last Updated
|
|
</Text>
|
|
<Text className="text-foreground text-sm font-sans-medium mt-0.5">
|
|
{member.updatedAt
|
|
? new Date(member.updatedAt).toLocaleDateString("en-GB", {
|
|
day: "numeric",
|
|
month: "short",
|
|
year: "numeric",
|
|
})
|
|
: "—"}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</CardContent>
|
|
</Card>
|
|
</ScrollView>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|