Skip to content

Commit

Permalink
add user filter
Browse files Browse the repository at this point in the history
  • Loading branch information
ngoerlitz committed Apr 14, 2024
1 parent c0a3b5c commit a0c6ba5
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 64 deletions.
1 change: 0 additions & 1 deletion backend/db/migrations/20221115171242-create-user-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const USER_TABLE_ATTRIBUTES = {
refresh_token: {
type: DataType.TEXT,
},

createdAt: DataType.DATE,
updatedAt: DataType.DATE,
};
Expand Down
18 changes: 17 additions & 1 deletion backend/src/models/User.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Model, InferAttributes, CreationOptional, InferCreationAttributes, NonAttribute, Association } from "sequelize";
import { sequelize } from "../core/Sequelize";
import { UserData } from "./UserData";
import { IUserData, UserData } from "./UserData";
import { UserSettings } from "./UserSettings";
import { MentorGroup } from "./MentorGroup";
import { TrainingSession } from "./TrainingSession";
Expand All @@ -18,6 +18,22 @@ import { UserSolo } from "./UserSolo";
import { USER_TABLE_ATTRIBUTES, USER_TABLE_NAME } from "../../db/migrations/20221115171242-create-user-table";
import { UserNote } from "./UserNote";

export type IMinimalUser = Pick<ISensitiveUser, "id" | "first_name" | "last_name">;
export type IUser = Omit<ISensitiveUser, "access_token" | "refresh_token">;

export interface ISensitiveUser {
id: number;
first_name: string;
last_name: string;
email: string;
access_token?: string;
refresh_token?: string;
createdAt?: Date;
updatedAt?: Date;

user_data?: IUserData;
}

export class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
//
// Attributes
Expand Down
16 changes: 16 additions & 0 deletions backend/src/models/UserData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ import { User } from "./User";
import { sequelize } from "../core/Sequelize";
import { USER_DATA_TABLE_ATTRIBUTES, USER_DATA_TABLE_NAME } from "../../db/migrations/20221115171243-create-user-data-table";

export interface IUserData {
user_id: number;
rating_atc: number;
rating_pilot: number;
country_code?: string;
country_name?: string;
region_code?: string;
region_name?: string;
division_code?: string;
division_name?: string;
subdivision_code?: string;
subdivision_name?: string;
createdAt?: Date;
updatedAt?: Date;
}

export class UserData extends Model<InferAttributes<UserData>, InferCreationAttributes<UserData>> {
//
// Attributes
Expand Down
19 changes: 17 additions & 2 deletions backend/src/models/UserSession.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { Model, InferAttributes, CreationOptional, InferCreationAttributes, ForeignKey, NonAttribute, Association } from "sequelize";
import { User } from "./User";
import { DataType } from "sequelize-typescript";
import { ISensitiveUser, IUser, User } from "./User";
import { sequelize } from "../core/Sequelize";
import { USER_SESSION_ATTRIBUTES, USER_SESSION_TABLE_NAME } from "../../db/migrations/20221115171243-create-user-session-table";

export interface IUserSession {
id: number;
uuid: string;
browser_uuid: string;
client: string;
user_id: number;
expires_at: Date;
expires_latest: Date;
createdAt?: Date;
updatedAt?: Date;

// The login session is pretty sensitive anyway, so we might as well add the
// sensitive user here
user?: ISensitiveUser;
}

export class UserSession extends Model<InferAttributes<UserSession>, InferCreationAttributes<UserSession>> {
//
// Attributes
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/components/ui/UserFilter/UserFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import useApi from "@/utils/hooks/useApi";
import { Table } from "@/components/ui/Table/Table";
import { Input } from "@/components/ui/Input/Input";
import { useFilter } from "@/utils/hooks/useFilter";
import { useMemo, useState } from "react";
import { useDebounce } from "@/utils/hooks/useDebounce";
import { fuzzySearch } from "@/utils/helper/fuzzysearch/FuzzySearchHelper";
import UserFilterTypes from "@/components/ui/UserFilter/UserFilter.types";
import { RenderIf } from "@/components/conditionals/RenderIf";
import { IMinimalUser } from "@models/User";

interface IUserFilterProps {
onUserSelect: (user: IMinimalUser) => any;
removeIDs?: number[];
}

function filterFunction(user: IMinimalUser, searchValue: string) {
return fuzzySearch(searchValue, [user.first_name + " " + user.last_name, user.id.toString()]).length > 0;
}

export function UserFilter(props: IUserFilterProps) {
const { data: users, loading: loadingUsers } = useApi<IMinimalUser[]>({
url: "/administration/user/min",
method: "GET",
});

const [searchValue, setSearchValue] = useState<string>("");
const debouncedSearchValue = useDebounce<string>(searchValue);

const filteredData = useFilter<IMinimalUser>(
users?.filter(u => !props.removeIDs?.includes(u.id)) ?? [],
searchValue,
debouncedSearchValue,
filterFunction,
true
);

return (
<>
<Input label={"Benutzer Suchen"} labelSmall placeholder={"CID, Name"} value={searchValue} onChange={e => setSearchValue(e.target.value)} />

<RenderIf
truthValue={searchValue != ""}
elementTrue={
<Table
className={"mt-3"}
paginationPerPage={5}
loading={loadingUsers || debouncedSearchValue != searchValue}
paginate
columns={UserFilterTypes.getColumns(props.onUserSelect)}
data={filteredData}
/>
}
/>
</>
);
}
37 changes: 37 additions & 0 deletions frontend/src/components/ui/UserFilter/UserFilter.types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TableColumn } from "react-data-table-component";
import { Button } from "@/components/ui/Button/Button";
import { COLOR_OPTS, ICON_SIZE_OPTS, SIZE_OPTS } from "@/assets/theme.config";
import { TbArrowRight } from "react-icons/tb";
import { IMinimalUser } from "@models/User";

function getColumns(onUserSelect: (u: IMinimalUser) => any): TableColumn<IMinimalUser>[] {
return [
{
name: "CID",
selector: row => row.id.toString(),
},
{
name: "Name",
selector: row => `${row.first_name} ${row.last_name}`,
},
{
name: "Aktion",
cell: row => {
return (
<Button
variant={"twoTone"}
color={COLOR_OPTS.PRIMARY}
size={SIZE_OPTS.SM}
icon={<TbArrowRight size={ICON_SIZE_OPTS.SM} />}
onClick={() => onUserSelect(row)}>
Auswählen
</Button>
);
},
},
];
}

export default {
getColumns,
};
2 changes: 2 additions & 0 deletions frontend/src/models/UserModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { TrainingRequestModel } from "@/models/TrainingRequestModel";
import { TrainingSessionModel } from "@/models/TrainingSessionModel";
import { TrainingLogModel } from "@/models/TrainingSessionBelongsToUser.model";

export type MinimalUser = Pick<UserModel, "id" | "first_name" | "last_name">;

export type UserModel = {
id: number;
first_name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { COLOR_OPTS, SIZE_OPTS } from "@/assets/theme.config";
import { Separator } from "@/components/ui/Separator/Separator";
import React, { FormEvent, useState } from "react";
import FormHelper from "../../../../../utils/helper/FormHelper";
import { UserModel } from "@/models/UserModel";
import { MinimalUser, UserModel } from "@/models/UserModel";
import { Table } from "@/components/ui/Table/Table";
import { MentorGroupModel } from "@/models/MentorGroupModel";
import ToastHelper from "../../../../../utils/helper/ToastHelper";
Expand All @@ -18,9 +18,10 @@ import { CommonConstants, CommonRegexp } from "@/core/Config";
import { useUserSelector } from "@/app/features/authSlice";
import MGCUsersTableTypes from "@/pages/administration/lm/mentor-group/create/_types/MGCUsersTable.types";
import { axiosInstance } from "@/utils/network/AxiosInstance";
import { UserFilter } from "@/components/ui/UserFilter/UserFilter";

export interface IUserInMentorGroup {
user: UserModel;
user: MinimalUser;
admin: boolean;
can_manage: boolean;
}
Expand All @@ -29,7 +30,7 @@ export function MentorGroupCreateView() {
const navigate = useNavigate();
const user = useUserSelector();

const defaultUser: IUserInMentorGroup = { user: user ?? ({} as UserModel), admin: true, can_manage: true };
const defaultUser: IUserInMentorGroup = { user: user ?? ({} as MinimalUser), admin: true, can_manage: true };
const [newUserID, setNewUserID] = useState<string>("");
const [loadingUser, setLoadingUser] = useState<boolean>(false);
const [users, setUsers] = useState<IUserInMentorGroup[]>([defaultUser]);
Expand Down Expand Up @@ -64,36 +65,13 @@ export function MentorGroupCreateView() {
.finally(() => setSubmitting(false));
}

function addUser() {
setLoadingUser(true);

axiosInstance
.get(`/administration/user/data/basic`, {
params: { user_id: newUserID },
})
.then((res: AxiosResponse) => {
const newUser = {
user: res.data as UserModel,
admin: false,
can_manage: false,
};
setUsers([...users, newUser]);
})
.catch(() => {
ToastHelper.error(`Fehler beim Laden des Benutzers mit der ID ${newUserID}`);
})
.finally(() => {
setLoadingUser(false);
setNewUserID("");
});
}

function removeUser(user: UserModel) {
const newUsers = users.filter(u => {
return u.user.id != user.id;
});

setUsers(newUsers);
function addUser(u: MinimalUser) {
const newUser = {
user: { ...u },
admin: false,
can_manage: false,
};
setUsers([...users, newUser]);
}

return (
Expand Down Expand Up @@ -140,29 +118,7 @@ export function MentorGroupCreateView() {
</Card>

<Card className={"mt-5"} header={"Mitglieder"} headerBorder>
<Input
onChange={e => setNewUserID(e.target.value)}
regex={CommonRegexp.CID}
maxLength={CommonConstants.CID_MAX_LEN}
label={"Benutzer Hinzufügen"}
labelSmall
inputError={users.length == 0}
preIcon={<TbUser size={20} />}
placeholder={users[0]?.user.id.toString() ?? "1373921"}
/>

<Button
size={SIZE_OPTS.SM}
color={COLOR_OPTS.PRIMARY}
loading={loadingUser}
disabled={submitting}
variant={"twoTone"}
className={"mt-3"}
onClick={() => {
addUser();
}}>
Hinzufügen
</Button>
<UserFilter onUserSelect={addUser} removeIDs={users.map(u => u.user.id)} />

<Separator />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ function getColumns(my_user_id: number | undefined, users: IUserInMentorGroup[],
{
name: "Aktion",
cell: row => {
if (isEditing && row.user.id != my_user_id) {
if (row.user.id == my_user_id) {
return <></>;
}

if (isEditing) {
return (
<Button
variant={"twoTone"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ export function CAVRequestTrainingModal(props: RequestTrainingModalPartialProps)

<RenderIf
truthValue={nextTraining?.description != null}
elementTrue={
<TextArea className={"mt-5"} label={"Beschreibung"} labelSmall disabled value={nextTraining?.description} />
}
elementTrue={<TextArea className={"mt-5"} label={"Beschreibung"} labelSmall disabled value={nextTraining?.description} />}
/>

<Separator />
Expand Down

0 comments on commit a0c6ba5

Please sign in to comment.