project setup

This commit is contained in:
Sone
2025-11-02 10:08:56 +01:00
commit 568838003e
174 changed files with 27100 additions and 0 deletions
@@ -0,0 +1,46 @@
import { Divider } from "react-native-paper";
import { User } from "@/store/models/User.model";
import { PersonalInfo } from "@/components/features/accounts/PersonalInfo";
import { PersonalMetrics } from "@/components/features/accounts/PersonalMetrics";
import { PersonalTodoList } from "@/components/features/accounts/PersonalTodoList";
import { PersonalLoginList } from "@/components/features/accounts/PersonalLoginList";
import { PersonalActions } from "@/components/features/accounts/PersonalActions";
import { useAppSelector } from "@/store/hooks";
import { selectIsMobile } from "@/store/screen/screenSizeSlice";
interface AccountDetailsProps {
user: User;
isLoading: boolean;
}
export const AccountDetails = ({ user, isLoading }: AccountDetailsProps) => {
const isMobile = useAppSelector(selectIsMobile);
return (
<>
<PersonalActions user={user} isLoading={isLoading} />
<Divider
style={{ height: 6, backgroundColor: "#C4BEBE" }}
className={isMobile ? "my-1" : "my-4"}
/>
<PersonalInfo user={user} manager={user.manager} />
<Divider
style={{ height: 6, backgroundColor: "#C4BEBE" }}
className={isMobile ? "my-1" : "my-4"}
/>
<PersonalMetrics user={user} />
<Divider
style={{ height: 6, backgroundColor: "#C4BEBE" }}
className={isMobile ? "my-1" : "my-4"}
/>
<PersonalTodoList user={user} />
<Divider
style={{ height: 6, backgroundColor: "#C4BEBE" }}
className={isMobile ? "my-1" : "my-4"}
/>
<PersonalLoginList user={user} />
</>
);
};
+33
View File
@@ -0,0 +1,33 @@
import { ReactNode } from "react";
import { View, Text } from "react-native";
import { useAppSelector } from "@/store/hooks";
import { selectIsMobile } from "@/store/screen/screenSizeSlice";
interface InfoRowProps {
label: string;
value: string | ReactNode;
}
export const InfoRow = ({ label, value }: InfoRowProps) => {
const isMobile = useAppSelector(selectIsMobile);
return (
<View className="flex-row mb-2 items-center">
<Text
className={`font-medium mb-2 mr-[2px] text-gray-600 ${
isMobile ? "w-[50%]" : "w-[28%]"
}`}
>
{label}:
</Text>
<Text
className={`font-medium mb-2 text-black ${
isMobile ? "w-[50%]" : "w-[72%]"
}`}
>
{value || "-"}
</Text>
</View>
);
};
@@ -0,0 +1,169 @@
import { useState } from "react";
import { View } from "react-native";
import { IconButton } from "react-native-paper";
import { User } from "@/store/models/User.model";
import { UserModal } from "@/components/features/users/UserModal";
import { DeleteUserModal } from "@/components/features/users/DeleteUserModal";
import { LockUserModal } from "@/components/features/users/LockUserModal";
import { AdminModal } from "@/components/features/admins/AdminModal";
import { OperationMode } from "@/constants/operationModeEnum";
import { RoleEnum } from "@/constants/roleEnum";
import {
useUpdateUserMutation,
useDeleteUserMutation,
useLockUserMutation,
useGetUserByIdQuery,
} from "@/store/api/usersApi";
import { AppSnackbar } from "@/components/ui/AppSnackbar";
interface PersonalActionsProps {
user: User;
isLoading: boolean;
}
export const PersonalActions = ({ user, isLoading }: PersonalActionsProps) => {
const [editModalVisible, setEditModalVisible] = useState(false);
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const [lockModalVisible, setLockModalVisible] = useState(false);
const [snackbarVisible, setSnackbarVisible] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState("");
const { refetch } = useGetUserByIdQuery(user.id, { skip: !user.id });
const [updateUser, { isLoading: isUpdating }] = useUpdateUserMutation();
const [deleteUser, { isLoading: isDeleting }] = useDeleteUserMutation();
const [lockUser, { isLoading: isLocking }] = useLockUserMutation();
const showSnackbar = (message: string) => {
setSnackbarMessage(message);
setSnackbarVisible(true);
};
const handleEdit = () => {
setEditModalVisible(true);
};
const handleDelete = () => {
setDeleteModalVisible(true);
};
const handleLock = () => {
setLockModalVisible(true);
};
const handleUpdateUser = async (updatedUser: Partial<User>) => {
try {
await updateUser(updatedUser).unwrap();
showSnackbar(
`${updatedUser.firstName} ${updatedUser.lastName} updated successfully`
);
setEditModalVisible(false);
refetch();
} catch (error) {
showSnackbar("Error updating user");
console.error("Error updating user:", error);
}
};
const handleDeleteUser = async (userToDelete: User) => {
try {
await deleteUser(userToDelete.id).unwrap();
showSnackbar(
`${userToDelete.firstName} ${userToDelete.lastName} deleted successfully`
);
setDeleteModalVisible(false);
refetch();
} catch (error) {
showSnackbar("Error deleting user");
console.error("Error deleting user:", error);
}
};
const handleLockUser = async (userToLock: User) => {
try {
await lockUser(userToLock.id).unwrap();
const action = userToLock.isLocked ? "unlocked" : "locked";
showSnackbar(
`${userToLock.firstName} ${userToLock.lastName} ${action} successfully`
);
setLockModalVisible(false);
refetch();
} catch (error) {
showSnackbar("Error updating user lock status");
console.error("Error locking/unlocking user:", error);
}
};
return (
<>
<View className="flex-row justify-end">
<IconButton
icon="pencil-outline"
onPress={handleEdit}
size={24}
iconColor="#7A757C"
disabled={isLoading}
/>
<IconButton
icon="lock-outline"
onPress={handleLock}
size={24}
iconColor={user.isLocked ? "#F83434" : "#7A757C"}
disabled={isLoading}
/>
<IconButton
icon="delete-outline"
onPress={handleDelete}
size={24}
iconColor="#7A757C"
disabled={isLoading}
/>
</View>
{user.role === RoleEnum.ADMIN || user.role === RoleEnum.MANAGER ? (
<AdminModal
visible={editModalVisible}
onClose={() => setEditModalVisible(false)}
onSubmit={handleUpdateUser}
admin={user}
mode={OperationMode.EDIT}
isLoading={isUpdating}
/>
) : (
<UserModal
visible={editModalVisible}
onClose={() => setEditModalVisible(false)}
onSubmit={handleUpdateUser}
user={user}
mode={OperationMode.EDIT}
isLoading={isUpdating}
/>
)}
<DeleteUserModal
visible={deleteModalVisible}
user={user}
onDismiss={() => setDeleteModalVisible(false)}
onDelete={handleDeleteUser}
isLoading={isDeleting}
/>
<LockUserModal
visible={lockModalVisible}
user={user}
onDismiss={() => setLockModalVisible(false)}
onLock={handleLockUser}
isLoading={isLocking}
/>
<AppSnackbar
visible={snackbarVisible}
onDismiss={() => setSnackbarVisible(false)}
action={{ label: "Close", onPress: () => setSnackbarVisible(false) }}
>
{snackbarMessage}
</AppSnackbar>
</>
);
};
@@ -0,0 +1,55 @@
import { View } from "react-native";
import { Avatar } from "react-native-paper";
import { User } from "@/store/models/User.model";
import { getInitials, getRoleLabel } from "@/utils/userUtils";
import { InfoRow } from "@/components/features/accounts/InfoRow";
import { useAppSelector } from "@/store/hooks";
import { selectIsMobile } from "@/store/screen/screenSizeSlice";
interface PersonalInfoProps {
user: User;
manager?: User | null;
}
export const PersonalInfo = ({ user, manager }: PersonalInfoProps) => {
const isMobile = useAppSelector(selectIsMobile);
const managerFullName =
manager && manager?.firstName + " " + manager?.lastName;
return (
<View className="bg-lightGray rounded-md">
<View className="flex-row items-center justify-between mb-4">
<View className="flex-row items-center">
{user.imageUrl ? (
<Avatar.Image source={{ uri: user.imageUrl }} size={56} />
) : (
<Avatar.Text
label={getInitials(user.firstName, user.lastName)}
size={50}
/>
)}
</View>
</View>
<View className={`gap-6 ${isMobile ? "flex-col" : "flex-row"}`}>
<View className="flex-1">
<InfoRow label="First Name" value={user.firstName || "-"} />
<InfoRow label="Middle Name" value={user.middleName || "-"} />
<InfoRow label="Last Name" value={user.lastName || "-"} />
</View>
<View className="flex-1">
<InfoRow label="Email" value={user.email || "-"} />
<InfoRow label="Phone Number" value={user.phoneNumber || "-"} />
<InfoRow label="Department" value={user.department || "-"} />
</View>
<View className="flex-1">
<InfoRow label="Role" value={getRoleLabel(user.role)} />
<InfoRow label="Reports To" value={managerFullName || "-"} />
</View>
</View>
</View>
);
};
@@ -0,0 +1,16 @@
import { View, Text } from "react-native";
import { User } from "@/store/models/User.model";
interface PersonalLoginListProps {
user: User;
}
export const PersonalLoginList = ({ user }: PersonalLoginListProps) => {
return (
<View className="bg-lightGray rounded-md">
<Text className="font-medium my-4 text-gray-600">Log-in List:</Text>
<Text className="font-medium text-black">No logins assigned</Text>
</View>
);
};
@@ -0,0 +1,84 @@
import { View, Text } from "react-native";
import { User } from "@/store/models/User.model";
import { InfoRow } from "./InfoRow";
import { useAppSelector } from "@/store/hooks";
import { selectIsMobile } from "@/store/screen/screenSizeSlice";
import { useGetUserLogsQuery } from "@/store/api/usersApi";
import { formatDate, formatTime } from "@/utils/dateUtils";
import { RiskIndicator } from "@/components/ui/RiskIndicator";
import { SizeEnum } from "@/constants/sizeEnum";
import { RiskEnum, RiskLabels } from "@/constants/riskEnum";
import { SecurityTypeLabels } from "@/constants/securityTypeEnum";
interface PersonalMetricsProps {
user: User;
}
export const PersonalMetrics = ({ user }: PersonalMetricsProps) => {
const isMobile = useAppSelector(selectIsMobile);
const {
data: logsData,
isLoading: logsLoading,
isError: logsError,
} = useGetUserLogsQuery(
{ page: 1, limit: 1, search: user.email },
{ skip: !user?.email }
);
const lastLogItem = logsData?.items?.[0];
const lastLoginAt = lastLogItem?.lastLoginAt;
const activity = lastLogItem?.activity;
const lastLoginDisplay = logsLoading
? "Loading..."
: logsError
? "-"
: lastLoginAt
? `${formatDate(lastLoginAt)} ${formatTime(lastLoginAt)}`
: "-";
const lastPasswordChangeDisplay = logsLoading
? "Loading..."
: logsError
? "-"
: activity === "Password reset" && lastLoginAt
? `${formatDate(lastLoginAt)} ${formatTime(lastLoginAt)}`
: "-";
return (
<View className="bg-lightGray rounded-md">
<View className={`gap-4 ${isMobile ? "flex-col" : "flex-row"}`}>
<View className="flex-1 mt-4">
<InfoRow label="Last Log-in" value={lastLoginDisplay} />
<InfoRow
label="Last Password Change"
value={lastPasswordChangeDisplay}
/>
</View>
<View className="flex-1 mt-4">
{user.riskStatus && (
<InfoRow
label="Risk Status"
value={
<View className="flex-row items-baseline gap-4">
<Text>
{user.riskStatus === RiskEnum.NO_RISK
? RiskLabels[user.riskStatus]
: RiskLabels[user.riskStatus].split(" ")[0]}
</Text>
<RiskIndicator risk={user.riskStatus} size={SizeEnum.Small} />
</View>
}
/>
)}
<InfoRow
label="Security Type"
value={SecurityTypeLabels[user.securityType]}
/>
</View>
</View>
</View>
);
};
@@ -0,0 +1,69 @@
import { View, Text } from "react-native";
import { useGetTodosByUserQuery } from "@/store/api/todosApi";
import { PriorityIndicator } from "@/components/ui/PriorityIndicator";
import { SizeEnum } from "@/constants/sizeEnum";
import { StatusTableItem } from "@/components/ui/StatusTableItem";
import { User } from "@/store/models/User.model";
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
import { NoData } from "@/components/ui/NoData";
interface PersonalTodoListProps {
user: User;
}
export const PersonalTodoList = ({ user }: PersonalTodoListProps) => {
const {
data: todos,
isLoading,
isFetching,
error,
refetch,
} = useGetTodosByUserQuery(user.id, { skip: !user.id });
return (
<View className="bg-lightGray rounded-md">
<Text className="font-medium my-4 text-gray-600">To-Do List:</Text>
{isLoading ? (
<LoadingSpinner />
) : error ? (
<NoData
onRetry={refetch}
message="Failed to load todos"
isRetrying={isFetching}
/>
) : todos && todos.length > 0 ? (
<View style={{ gap: 8 }}>
{todos.map((todo) => (
<View
key={todo.id}
className="bg-white rounded-lg p-4 mb-3 shadow-sm"
>
<View className="flex-row items-center justify-between mb-2">
<Text
className="text-base font-medium flex-1 mr-2"
numberOfLines={10}
>
{todo.task}
</Text>
<View className="flex-row items-center gap-2">
<StatusTableItem todoStatus={todo.status} />
<PriorityIndicator
priority={todo.priority}
size={SizeEnum.Small}
/>
</View>
</View>
</View>
))}
</View>
) : (
<Text className="font-medium text-black">
{user.role === "USER"
? "No todos assigned for this user"
: "No todos assigned"}
</Text>
)}
</View>
);
};