initial commit
This commit is contained in:
@@ -17,8 +17,8 @@ export const UserButton = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const initials = useMemo(
|
||||
() => getInitials(user?.firstName, user?.lastName),
|
||||
[user?.firstName, user?.lastName]
|
||||
() => getInitials(user?.firstName!, user?.lastName!),
|
||||
[user?.firstName, user?.lastName],
|
||||
);
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
@@ -14,32 +14,28 @@ interface AccountDetailsProps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const backgroundStyle = {
|
||||
height: 6,
|
||||
backgroundColor: "#C4BEBE",
|
||||
};
|
||||
|
||||
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"}
|
||||
/>
|
||||
<Divider style={backgroundStyle} className={isMobile ? "my-1" : "my-4"} />
|
||||
|
||||
<PersonalInfo user={user} manager={user.manager} />
|
||||
<Divider
|
||||
style={{ height: 6, backgroundColor: "#C4BEBE" }}
|
||||
className={isMobile ? "my-1" : "my-4"}
|
||||
/>
|
||||
<Divider style={backgroundStyle} className={isMobile ? "my-1" : "my-4"} />
|
||||
|
||||
<PersonalMetrics user={user} />
|
||||
<Divider
|
||||
style={{ height: 6, backgroundColor: "#C4BEBE" }}
|
||||
className={isMobile ? "my-1" : "my-4"}
|
||||
/>
|
||||
<Divider style={backgroundStyle} className={isMobile ? "my-1" : "my-4"} />
|
||||
|
||||
<PersonalTodoList user={user} />
|
||||
<Divider
|
||||
style={{ height: 6, backgroundColor: "#C4BEBE" }}
|
||||
className={isMobile ? "my-1" : "my-4"}
|
||||
/>
|
||||
<Divider style={backgroundStyle} className={isMobile ? "my-1" : "my-4"} />
|
||||
|
||||
<PersonalLoginList user={user} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -43,11 +43,11 @@ export const AdminModal = ({
|
||||
roleType: "admin",
|
||||
limit: 1000,
|
||||
},
|
||||
{ skip: !visible }
|
||||
{ skip: !visible },
|
||||
);
|
||||
const managerOptions = useMemo(
|
||||
() => (adminsData?.items || []).filter((u) => u.role === RoleEnum.MANAGER),
|
||||
[adminsData]
|
||||
[adminsData],
|
||||
);
|
||||
|
||||
const adminRef = useRef<User | null>(admin);
|
||||
@@ -329,7 +329,7 @@ export const AdminModal = ({
|
||||
<Button
|
||||
onPress={handleSubmit(submit)}
|
||||
disabled={isLoading || !isValid || (isEdit && !isDirty)}
|
||||
className="!ml-2"
|
||||
className="ml-2!"
|
||||
>
|
||||
{submitText}
|
||||
</Button>
|
||||
|
||||
@@ -52,7 +52,7 @@ export const DesktopAdminAccountsTable = ({
|
||||
{admins.map((admin) => (
|
||||
<DataTable.Row key={admin.id}>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<View className="flex-row items-center shrink pr-2">
|
||||
{admin.imageUrl ? (
|
||||
<Avatar.Image
|
||||
source={{
|
||||
@@ -68,11 +68,11 @@ export const DesktopAdminAccountsTable = ({
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
)}
|
||||
<View className="flex-row items-center flex-shrink px-1 py-1">
|
||||
<View className="flex-row items-center shrink px-1 py-1">
|
||||
<Text
|
||||
onPress={() => openDetails(admin)}
|
||||
numberOfLines={10}
|
||||
className="flex-shrink"
|
||||
className="shrink"
|
||||
>
|
||||
{admin.firstName} {admin.lastName}
|
||||
</Text>
|
||||
@@ -93,7 +93,7 @@ export const DesktopAdminAccountsTable = ({
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<View className="flex-row items-center shrink pr-2">
|
||||
<Text numberOfLines={2}>{admin.email}</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
@@ -102,7 +102,7 @@ export const DesktopAdminAccountsTable = ({
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<View className="flex-row items-center flex-shrink">
|
||||
<View className="flex-row items-center shrink">
|
||||
<Text numberOfLines={2}>
|
||||
{admin.role === RoleEnum.ADMIN ? "Admin" : "Manager"}
|
||||
</Text>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { View, Text } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { router } from "expo-router";
|
||||
|
||||
import { useAppSelector } from "@/store/hooks";
|
||||
import { selectIsMobile } from "@/store/screen/screenSizeSlice";
|
||||
|
||||
@@ -10,7 +11,9 @@ export const AccessDenied = () => {
|
||||
return (
|
||||
<View className="flex-1 bg-primaryPurpleLight">
|
||||
<View className={`${isMobile ? "p-1" : "p-5"} flex-1`}>
|
||||
<View className={`${isMobile ? "p-1" : "p-5"} bg-lightGray rounded-lg flex-1`}>
|
||||
<View
|
||||
className={`${isMobile ? "p-1" : "p-5"} bg-lightGray rounded-lg flex-1`}
|
||||
>
|
||||
<View className="flex-1 justify-center items-center p-4">
|
||||
<Text className="text-2xl font-bold mb-4 text-highRisk">
|
||||
Access Denied
|
||||
|
||||
@@ -31,27 +31,30 @@ export const DesktopLogsTable = ({
|
||||
{logs.map((log) => (
|
||||
<DataTable.Row key={log.id}>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<View className="flex-row items-center shrink pr-2">
|
||||
<Text numberOfLines={1}>{log.name}</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<View className="flex-row items-center shrink pr-2">
|
||||
<Text numberOfLines={1}>{log.email}</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<View className="flex-row items-center shrink pr-2">
|
||||
<Text numberOfLines={1}>
|
||||
{formatDate(log.lastLoginAt)} {formatTimeWithSeconds(log.lastLoginAt)}
|
||||
{formatDate(log.lastLoginAt)}{" "}
|
||||
{formatTimeWithSeconds(log.lastLoginAt)}
|
||||
</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<View className="flex-row items-center shrink pr-2">
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ color: log.status === "FAILURE" ? "#F83434" : "#22C927" }}
|
||||
style={{
|
||||
color: log.status === "FAILURE" ? "#F83434" : "#22C927",
|
||||
}}
|
||||
>
|
||||
{log.activity}
|
||||
{log.status === "FAILURE" && reasonLabel(log.failureReason)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { formatDate, formatTimeWithSeconds } from "@/utils/dateUtils";
|
||||
import { reasonLabel } from "@/utils/loginUtils";
|
||||
|
||||
interface MobileLogsTableProps {
|
||||
logs: UserLoginLog[];
|
||||
logs: UserLoginLog[] | null | undefined;
|
||||
}
|
||||
|
||||
export const MobileLogsTable = ({ logs }: MobileLogsTableProps) => {
|
||||
@@ -15,18 +15,21 @@ export const MobileLogsTable = ({ logs }: MobileLogsTableProps) => {
|
||||
className="flex-1"
|
||||
contentContainerStyle={{ paddingBottom: 16 }}
|
||||
>
|
||||
{logs.map((log) => (
|
||||
{logs?.map((log) => (
|
||||
<View key={log.id} className="mb-3 p-4 bg-white rounded-md shadow-sm">
|
||||
<Text className="text-base font-semibold">
|
||||
{log.name}{" "}
|
||||
<Text className="font-normal text-gray-600">({log.email})</Text>
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-700 mt-1">
|
||||
Time: {formatDate(log.lastLoginAt)} {formatTimeWithSeconds(log.lastLoginAt)}
|
||||
Time: {formatDate(log.lastLoginAt)}{" "}
|
||||
{formatTimeWithSeconds(log.lastLoginAt)}
|
||||
</Text>
|
||||
<Text
|
||||
className="text-sm mt-1"
|
||||
style={{ color: log.status === "FAILURE" ? "#F83434" : "#22C927" }}
|
||||
style={{
|
||||
color: log.status === "FAILURE" ? "#F83434" : "#22C927",
|
||||
}}
|
||||
>
|
||||
Login activity: {log.activity}
|
||||
{log.status === "FAILURE" && reasonLabel(log.failureReason)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { SearchResult } from "@/store/api/searchApi";
|
||||
import { normalizeEnumLike } from "@/utils/formattingUtils";
|
||||
import { memo } from "react";
|
||||
import { Text, View, TouchableOpacity } from "react-native";
|
||||
import { Card, Divider } from "react-native-paper";
|
||||
|
||||
import { SearchResult } from "@/store/api/searchApi";
|
||||
import { normalizeEnumLike } from "@/utils/formattingUtils";
|
||||
|
||||
interface ResultRowProps {
|
||||
item: SearchResult;
|
||||
expanded: boolean;
|
||||
|
||||
@@ -68,7 +68,7 @@ export const DeleteTodoModal = ({
|
||||
{error && <Text className="text-highRisk mt-2">{error}</Text>}
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button className="!ml-2" onPress={handleDelete} disabled={isLoading}>
|
||||
<Button className="ml-2!" onPress={handleDelete} disabled={isLoading}>
|
||||
Delete
|
||||
</Button>
|
||||
</Dialog.Actions>
|
||||
|
||||
@@ -48,14 +48,14 @@ export const DesktopAdminTodosTable = ({
|
||||
{todos.map((todo) => (
|
||||
<DataTable.Row key={todo.id}>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink px-1 py-1">
|
||||
<View className="flex-row items-center shrink px-1 py-1">
|
||||
<Text onPress={() => openDetails(todo)} numberOfLines={10}>
|
||||
{todo.title}
|
||||
</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink px-1 py-1">
|
||||
<View className="flex-row items-center shrink px-1 py-1">
|
||||
<Text numberOfLines={10}>
|
||||
{todo.task && todo.task.length > 50
|
||||
? `${todo.task.slice(0, 50)}...`
|
||||
@@ -79,7 +79,7 @@ export const DesktopAdminTodosTable = ({
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell style={{ justifyContent: "center" }}>
|
||||
<Text className="px-1 py-1">{`${formatDateMDY(
|
||||
todo.createdAt
|
||||
todo.createdAt,
|
||||
)} ${formatTime(todo.createdAt)}`}</Text>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell style={{ justifyContent: "center" }}>
|
||||
|
||||
@@ -32,7 +32,7 @@ interface TodoModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (
|
||||
todo: Partial<Todo> & { assigneeIds?: string[]; userId?: string }
|
||||
todo: Partial<Todo> & { assigneeIds?: string[]; userId?: string },
|
||||
) => void;
|
||||
isLoading: boolean;
|
||||
todo?: Todo | null;
|
||||
@@ -51,9 +51,11 @@ export const TodoModal = ({
|
||||
todoRef.current = todo;
|
||||
// Helpers to extract existing selections from the todo
|
||||
const existingSupervisors = (todo?.assignees ?? []).filter(
|
||||
(u) => u.role === RoleEnum.ADMIN || u.role === RoleEnum.MANAGER
|
||||
(u) => u.role === RoleEnum.ADMIN || u.role === RoleEnum.MANAGER,
|
||||
);
|
||||
const existingUser = (todo?.assignees ?? []).find(
|
||||
(u) => u.role === RoleEnum.USER,
|
||||
);
|
||||
const existingUser = (todo?.assignees ?? []).find((u) => u.role === RoleEnum.USER);
|
||||
const defaultValues: TodoFormValues = {
|
||||
id: todo?.id,
|
||||
title: todo?.title ?? "",
|
||||
@@ -126,10 +128,12 @@ export const TodoModal = ({
|
||||
sortDir: "asc" as any,
|
||||
search: adminSearchQuery.trim(),
|
||||
},
|
||||
{ skip: !visible || adminSearchQuery.trim().length < 2 }
|
||||
{ skip: !visible || adminSearchQuery.trim().length < 2 },
|
||||
);
|
||||
const adminUsers = adminUsersPage?.items ?? [];
|
||||
const preselectedSupervisorItems = existingSupervisors.map(mapUserToDropdownItem);
|
||||
const preselectedSupervisorItems = existingSupervisors.map(
|
||||
mapUserToDropdownItem,
|
||||
);
|
||||
const adminDropdownItems = uniqueById([
|
||||
...preselectedSupervisorItems,
|
||||
...adminUsers.map(mapUserToDropdownItem),
|
||||
@@ -146,10 +150,12 @@ export const TodoModal = ({
|
||||
sortDir: "asc" as any,
|
||||
search: userSearchQuery.trim(),
|
||||
},
|
||||
{ skip: !visible || userSearchQuery.trim().length < 2 }
|
||||
{ skip: !visible || userSearchQuery.trim().length < 2 },
|
||||
);
|
||||
const regularUsers = regularUsersPage?.items ?? [];
|
||||
const preselectedUserItems = existingUser ? [mapUserToDropdownItem(existingUser)] : [];
|
||||
const preselectedUserItems = existingUser
|
||||
? [mapUserToDropdownItem(existingUser)]
|
||||
: [];
|
||||
const regularUserDropdownItems = uniqueById([
|
||||
...preselectedUserItems,
|
||||
...regularUsers.map(mapUserToDropdownItem),
|
||||
@@ -173,7 +179,7 @@ export const TodoModal = ({
|
||||
if (!visible) return;
|
||||
const t = todoRef.current;
|
||||
const admins = (t?.assignees ?? []).filter(
|
||||
(u) => u.role === RoleEnum.ADMIN || u.role === RoleEnum.MANAGER
|
||||
(u) => u.role === RoleEnum.ADMIN || u.role === RoleEnum.MANAGER,
|
||||
);
|
||||
const user = (t?.assignees ?? []).find((u) => u.role === RoleEnum.USER);
|
||||
const nextDefaults: TodoFormValues = {
|
||||
@@ -452,7 +458,7 @@ export const TodoModal = ({
|
||||
<Button
|
||||
onPress={handleSubmit(submit)}
|
||||
disabled={isLoading || !isValid || !isDirty}
|
||||
className="!ml-2"
|
||||
className="ml-2!"
|
||||
>
|
||||
{mode === OperationMode.ADD ? "Add" : "Edit"}
|
||||
</Button>
|
||||
|
||||
@@ -83,7 +83,7 @@ export const BulkDeleteUserModal = ({
|
||||
{error && <Text className="text-highRisk mt-2">{error}</Text>}
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button className="!ml-2" onPress={handleDelete} disabled={isLoading}>
|
||||
<Button className="ml-2!" onPress={handleDelete} disabled={isLoading}>
|
||||
{users.length === 1 ? "Delete" : "Delete All"}
|
||||
</Button>
|
||||
</Dialog.Actions>
|
||||
|
||||
@@ -63,7 +63,7 @@ export const DeleteUserModal = ({
|
||||
{error && <Text className="text-highRisk mt-2">{error}</Text>}
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button className="!ml-2" onPress={handleDelete} disabled={isLoading}>
|
||||
<Button className="ml-2!" onPress={handleDelete} disabled={isLoading}>
|
||||
Delete
|
||||
</Button>
|
||||
</Dialog.Actions>
|
||||
|
||||
@@ -65,7 +65,7 @@ export const DesktopUserAccountsTable = ({
|
||||
style={{ marginRight: 10, marginLeft: 10 }}
|
||||
/>
|
||||
)}
|
||||
<View className="flex-row items-center flex-shrink px-1 py-1">
|
||||
<View className="flex-row items-center shrink px-1 py-1">
|
||||
<Text
|
||||
numberOfLines={10}
|
||||
onPress={() => router.push(`/accounts/users/${user.id}`)}
|
||||
@@ -79,12 +79,12 @@ export const DesktopUserAccountsTable = ({
|
||||
<RiskIndicator risk={user.riskStatus} size={SizeEnum.Moderate} />
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink px-1 py-1">
|
||||
<View className="flex-row items-center shrink px-1 py-1">
|
||||
<Text numberOfLines={10}>{user.email}</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink px-1 py-1">
|
||||
<View className="flex-row items-center shrink px-1 py-1">
|
||||
<Text numberOfLines={10}>{user.title}</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
|
||||
@@ -66,7 +66,7 @@ export const LockUserModal = ({
|
||||
{error && <Text className="text-highRisk mt-2">{error}</Text>}
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button className="!ml-2" onPress={handleLock} disabled={isLoading}>
|
||||
<Button className="ml-2!" onPress={handleLock} disabled={isLoading}>
|
||||
{actionTitle}
|
||||
</Button>
|
||||
</Dialog.Actions>
|
||||
|
||||
@@ -87,7 +87,7 @@ export const UserModal = ({
|
||||
email: vals.email.trim().toLowerCase(),
|
||||
title: vals.title.trim(),
|
||||
riskStatus: vals.riskStatus,
|
||||
role: isEdit ? user?.role ?? RoleEnum.USER : RoleEnum.USER,
|
||||
role: isEdit ? (user?.role ?? RoleEnum.USER) : RoleEnum.USER,
|
||||
};
|
||||
return onSubmit(payload);
|
||||
};
|
||||
@@ -266,7 +266,7 @@ export const UserModal = ({
|
||||
<Button
|
||||
onPress={handleSubmit(submit)}
|
||||
disabled={isLoading || (isEdit && !isDirty)}
|
||||
className="!ml-2"
|
||||
className="ml-2!"
|
||||
>
|
||||
{submitText}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user