Selaa lähdekoodia

master pages

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 vuosi sitten
vanhempi
commit
afe7cac746
29 muutettua tiedostoa jossa 1062 lisäystä ja 446 poistoa
  1. +48
    -0
      src/app/(main)/settings/skill/create/page.tsx
  2. +50
    -0
      src/app/(main)/settings/skill/page.tsx
  3. +54
    -0
      src/app/(main)/settings/user/page.tsx
  4. +17
    -1
      src/app/api/skill/actions.ts
  5. +22
    -0
      src/app/api/skill/index.ts
  6. +3
    -3
      src/app/api/staff/actions.ts
  7. +3
    -4
      src/app/api/team/actions.ts
  8. +27
    -0
      src/app/api/user/actions.ts
  9. +43
    -0
      src/app/api/user/index.ts
  10. +122
    -0
      src/components/CreateSkill/CreateSkill.tsx
  11. +40
    -0
      src/components/CreateSkill/CreateSkillLoading.tsx
  12. +19
    -0
      src/components/CreateSkill/CreateSkillWrapper.tsx
  13. +90
    -0
      src/components/CreateSkill/SkillInfo.tsx
  14. +1
    -0
      src/components/CreateSkill/index.ts
  15. +1
    -1
      src/components/CreateTeam/CreateTeam.tsx
  16. +94
    -80
      src/components/CreateTeam/StaffAllocation.tsx
  17. +4
    -0
      src/components/NavigationContent/NavigationContent.tsx
  18. +96
    -0
      src/components/SkillSearch/SkillSearch.tsx
  19. +40
    -0
      src/components/SkillSearch/SkillSearchLoading.tsx
  20. +27
    -0
      src/components/SkillSearch/SkillSearchWrapper.tsx
  21. +1
    -0
      src/components/SkillSearch/index.ts
  22. +0
    -106
      src/components/StaffSearch/ConfirmDeleteModal.tsx
  23. +28
    -51
      src/components/StaffSearch/StaffSearch.tsx
  24. +0
    -105
      src/components/TeamSearch/ConfirmDeleteModal.tsx
  25. +74
    -95
      src/components/TeamSearch/TeamSearch.tsx
  26. +98
    -0
      src/components/UserSearch/UserSearch.tsx
  27. +40
    -0
      src/components/UserSearch/UserSearchLoading.tsx
  28. +19
    -0
      src/components/UserSearch/UserSearchWrapper.tsx
  29. +1
    -0
      src/components/UserSearch/index.ts

+ 48
- 0
src/app/(main)/settings/skill/create/page.tsx Näytä tiedosto

@@ -0,0 +1,48 @@
// 'use client';
import { I18nProvider, getServerI18n } from "@/i18n";
import CustomInputForm from "@/components/CustomInputForm";
import Check from "@mui/icons-material/Check";
import Close from "@mui/icons-material/Close";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Tab from "@mui/material/Tab";
import Tabs, { TabsProps } from "@mui/material/Tabs";
import { useRouter } from "next/navigation";
import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { Task, TaskTemplate } from "@/app/api/tasks";
import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions";
import { Error } from "@mui/icons-material";
import { ProjectCategory } from "@/app/api/projects";
import { Grid, Typography } from "@mui/material";
import CreateStaffForm from "@/components/CreateStaff/CreateStaff";
import CreateSkill from "@/components/CreateSkill";

// const Title = ["title1", "title2"];

const CreateStaff: React.FC = async () => {
const { t } = await getServerI18n("staff");

const title = ['', t('Additional Info')]
// const regex = new RegExp("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$")
// console.log(regex)

return (
<>
<Typography variant="h4">{t("Create Skill")}</Typography>
<I18nProvider namespaces={["skill"]}>
<CreateSkill
/>
</I18nProvider>
</>
);
};

export default CreateStaff;

+ 50
- 0
src/app/(main)/settings/skill/page.tsx Näytä tiedosto

@@ -0,0 +1,50 @@
import { preloadClaims } from "@/app/api/claims";
// import { preloadSkill, preloadTeamLeads } from "@/app/api/staff";
import SkillSearch from "@/components/SkillSearch";
import { I18nProvider, getServerI18n } from "@/i18n";
import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "Skill",
};

const Skill: React.FC = async () => {
const { t } = await getServerI18n("skill");
// preloadTeamLeads();
// preloadSkill();
return (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("Skill")}
</Typography>
<Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="/settings/skill/create"
>
{t("Create Skill")}
</Button>
</Stack>
<I18nProvider namespaces={["staff", "common"]}>
<Suspense fallback={<SkillSearch.Loading />}>
<SkillSearch />
</Suspense>
</I18nProvider>
</>
);
};

export default Skill;

+ 54
- 0
src/app/(main)/settings/user/page.tsx Näytä tiedosto

@@ -0,0 +1,54 @@
import { preloadClaims } from "@/app/api/claims";
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
import StaffSearch from "@/components/StaffSearch";
import TeamSearch from "@/components/TeamSearch";
import UserSearch from "@/components/UserSearch";
import { I18nProvider, getServerI18n } from "@/i18n";
import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";


export const metadata: Metadata = {
title: "User",
};


const User: React.FC = async () => {
const { t } = await getServerI18n("User");
// preloadTeamLeads();
// preloadStaff();
return (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("User")}
</Typography>
<Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="/settings/team/create"
>
{t("Create User")}
</Button>
</Stack>
<I18nProvider namespaces={["User", "common"]}>
<Suspense fallback={<UserSearch.Loading />}>
<UserSearch />
</Suspense>
</I18nProvider>
</>
);
};
export default User;

+ 17
- 1
src/app/api/skill/actions.ts Näytä tiedosto

@@ -5,6 +5,13 @@ import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface CreateSkillInputs {
id?: number;
name: String;
code: String;
description: String;
}

export interface comboProp {
id: any;
label: string;
@@ -18,4 +25,13 @@ export const fetchSkillCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/skill/combo`, {
next: { tags: ["skill"] },
});
});
});

export const saveSkill = async (data: CreateSkillInputs) => {
return serverFetchJson(`${BASE_API_URL}/skill/save`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};

+ 22
- 0
src/app/api/skill/index.ts Näytä tiedosto

@@ -0,0 +1,22 @@
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import "server-only";

export interface SkillResult {
action: any;
id: number;
name: string;
description: string;
code: string;
}

export const preloadSkill = () => {
fetchSkill();
};

export const fetchSkill = cache(async () => {
return serverFetchJson<SkillResult[]>(`${BASE_API_URL}/skill`, {
next: { tags: ["sill"] },
});
});

+ 3
- 3
src/app/api/staff/actions.ts Näytä tiedosto

@@ -1,5 +1,5 @@
"use server";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { StaffResult, data } from ".";
import { cache } from "react";
@@ -59,8 +59,8 @@ export const testing = async (data: CreateStaffInputs) => {
});
};
export const deleteStaff = async (data: StaffResult) => {
return serverFetchJson(`${BASE_API_URL}/staffs/delete/${data.id}`, {
export const deleteStaff = async (id: number) => {
return serverFetchWithNoContent(`${BASE_API_URL}/staffs/delete/${id}`, {
method: "DELETE",
// body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },


+ 3
- 4
src/app/api/team/actions.ts Näytä tiedosto

@@ -1,5 +1,5 @@
"use server";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import { TeamResult } from ".";
@@ -53,10 +53,9 @@ export const saveTeam = async (data: CreateTeamInputs) => {
};

export const deleteTeam = async (data: TeamResult) => {
return serverFetchJson(`${BASE_API_URL}/team/delete/${data.id}`, {
export const deleteTeam = async (id: number) => {
return serverFetchWithNoContent(`${BASE_API_URL}/team/delete/${id}`, {
method: "DELETE",
// body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};

+ 27
- 0
src/app/api/user/actions.ts Näytä tiedosto

@@ -0,0 +1,27 @@
"use server";

import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { revalidateTag } from "next/cache";
import { UserDetail, UserResult } from ".";
import { cache } from "react";

export interface UserInputs {
username: string;
firstname: string;
lastname: string;
}


export const fetchUserDetails = cache(async (id: number) => {
return serverFetchJson<UserDetail>(`${BASE_API_URL}/user/${id}`, {
next: { tags: ["user"] },
});
});

export const deleteUser = async (id: number) => {
return serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
});
};

+ 43
- 0
src/app/api/user/index.ts Näytä tiedosto

@@ -0,0 +1,43 @@
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import "server-only";


export interface UserResult {
action: any;
id: number;
name: string;
locale: string;
username: string;
fullName: string;
firstname: string;
lastname: string;
title: string;
department: string;
email: string;
phone1: string;
phone2: string;
remarks: string;
}

// export interface DetailedUser extends UserResult {
// username: string;
// password: string
// }

export interface UserDetail {
authIds: number[];
data: UserResult;
groupIds: number[];
}

export const preloadUser = () => {
fetchUser();
};

export const fetchUser = cache(async () => {
return serverFetchJson<UserResult[]>(`${BASE_API_URL}/user`, {
next: { tags: ["user"] },
});
});

+ 122
- 0
src/components/CreateSkill/CreateSkill.tsx Näytä tiedosto

@@ -0,0 +1,122 @@
"use client";

import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
import { Check, Close, RestartAlt } from "@mui/icons-material";
import { useCallback, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslation } from "react-i18next";
import { CreateSkillInputs, saveSkill } from "@/app/api/skill/actions";
import { Error } from "@mui/icons-material";
import SkillInfo from "./SkillInfo";

interface Props {}

const CreateSkill: React.FC<Props> = () => {
const formProps = useForm<CreateSkillInputs>();
const [serverError, setServerError] = useState("");
const router = useRouter();
const { t } = useTranslation();
const [tabIndex, setTabIndex] = useState(0);
const errors = formProps.formState.errors;

const onSubmit = useCallback<SubmitHandler<CreateSkillInputs>>(
async (data) => {
try {
console.log(data);
await saveSkill(data)
router.replace(`/settings/skill`)
} catch (e) {
console.log(e);
setServerError(t("An error has occurred. Please try again later."));
}
},
[router]
);

const handleCancel = () => {
router.back();
};

// const handleReset = useCallback(() => {
// console.log(defaultValues)
// }, [defaultValues])

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[]
);

const hasErrorsInTab = (
tabIndex: number,
errors: FieldErrors<CreateSkillInputs>
) => {
switch (tabIndex) {
case 0:
return Object.keys(errors).length > 0;
default:
false;
}
};
return (
<>
<FormProvider {...formProps}>
<Stack
spacing={2}
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
<Tabs
value={tabIndex}
onChange={handleTabChange}
variant="scrollable"
>
<Tab
label={t("Team Info")}
icon={
hasErrorsInTab(0, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />
) : undefined
}
iconPosition="end"
/>
{/* <Tab label={t("Certification")} iconPosition="end" /> */}
</Tabs>
{serverError && (
<Typography variant="body2" color="error" alignSelf="flex-end">
{serverError}
</Typography>
)}
{tabIndex === 0 && <SkillInfo />}
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
variant="outlined"
startIcon={<Close />}
onClick={handleCancel}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
startIcon={<Check />}
type="submit"
// disabled={Boolean(formProps.watch("isGridEditing"))}
>
{t("Confirm")}
</Button>
</Stack>
</Stack>
</FormProvider>
</>
);
};

export default CreateSkill;

+ 40
- 0
src/components/CreateSkill/CreateSkillLoading.tsx Näytä tiedosto

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const CreateSkillLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>CreateSkill
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default CreateSkillLoading;

+ 19
- 0
src/components/CreateSkill/CreateSkillWrapper.tsx Näytä tiedosto

@@ -0,0 +1,19 @@
import React from "react";
import CreateSkill from "./CreateSkill";
import CreateSkillLoading from "./CreateSkillLoading";
import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
import { useSearchParams } from "next/navigation";

interface SubComponents {
Loading: typeof CreateSkillLoading;
}

const CreateSkillWrapper: React.FC & SubComponents = async () => {


return <CreateSkill/>;
};

CreateSkillWrapper.Loading = CreateSkillLoading;

export default CreateSkillWrapper;

+ 90
- 0
src/components/CreateSkill/SkillInfo.tsx Näytä tiedosto

@@ -0,0 +1,90 @@
"use client";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import CardActions from "@mui/material/CardActions";
import RestartAlt from "@mui/icons-material/RestartAlt";
import Button from "@mui/material/Button";
import { Controller, useFormContext } from "react-hook-form";
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import { useCallback } from "react";
import { CreateSkillInputs } from "@/app/api/skill/actions";

const SkillInfo: React.FC = (
) => {
const { t } = useTranslation();
const {
register,
formState: { errors, defaultValues },
control,
reset,
resetField,
setValue,
} = useFormContext<CreateSkillInputs>();

const resetSkill = useCallback(() => {
console.log(defaultValues);
if (defaultValues !== undefined) {
resetField("name");
}
}, [defaultValues]);
return (
<>
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Skill Info")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Skill Name")}
fullWidth
rows={4}
{...register("name", {
required: true,
})}
error={Boolean(errors.name)}
helperText={Boolean(errors.name) && (errors.name?.message ? t(errors.name.message) : t("Please input correct name"))}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Skill Code")}
fullWidth
rows={4}
{...register("code", {
required: true,
})}
error={Boolean(errors.code)}
helperText={Boolean(errors.code) && (errors.code?.message ? t(errors.code.message) : t("Please input correct name"))}
/>
</Grid>
<Grid item xs={12}>
<TextField
label={t("Skill Description")}
fullWidth
multiline
rows={4}
{...register("description", {
required: true,
})}
error={Boolean(errors.description)}
helperText={Boolean(errors.description) && (errors.description?.message ? t(errors.description.message) : t("Please input correct name"))}
/>
</Grid>
</Grid>
</Box>
</CardContent>
</Card>
</>
);
};
export default SkillInfo;

+ 1
- 0
src/components/CreateSkill/index.ts Näytä tiedosto

@@ -0,0 +1 @@
export { default } from "./CreateSkillWrapper";

+ 1
- 1
src/components/CreateTeam/CreateTeam.tsx Näytä tiedosto

@@ -89,7 +89,7 @@ const hasErrorsInTab = (
}
iconPosition="end"
/>
<Tab label={t("Subsidiary Allocation")} iconPosition="end" />
<Tab label={t("Staff Allocation")} iconPosition="end" />
</Tabs>
{serverError && (
<Typography variant="body2" color="error" alignSelf="flex-end">


+ 94
- 80
src/components/CreateTeam/StaffAllocation.tsx Näytä tiedosto

@@ -18,9 +18,21 @@ import { StaffResult } from "@/app/api/staff";
import SearchResults, { Column } from "../SearchResults";
import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material";
import { Card } from "reactstrap";
import { Box, CardContent, Grid, IconButton, InputAdornment, Stack, Tab, Tabs, TabsProps, TextField, Typography } from "@mui/material";
import {
Box,
CardContent,
Grid,
IconButton,
InputAdornment,
Stack,
Tab,
Tabs,
TabsProps,
TextField,
Typography,
} from "@mui/material";
import { differenceBy } from "lodash";
import StarsIcon from '@mui/icons-material/Stars';
import StarsIcon from "@mui/icons-material/Stars";

export interface Props {
allStaffs: StaffResult[];
@@ -35,16 +47,15 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
reset,
resetField,
} = useFormContext<CreateTeamInputs>();
const initialStaffs = staff.map((s) => ({ ...s }));
// console.log(initialStaffs)
// console.log(initialStaffs)
const [filteredStaff, setFilteredStaff] = useState(initialStaffs);
const [selectedStaff, setSelectedStaff] = useState<typeof filteredStaff>(
initialStaffs.filter((s) => getValues("addStaffIds")?.includes(s.id))
);
const [seletedTeamLead, setSeletedTeamLead] = useState<number>()
// Adding / Removing staff

// Adding / Removing staff
const addStaff = useCallback((staff: StaffResult) => {
setSelectedStaff((s) => [...s, staff]);
}, []);
@@ -53,27 +64,31 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
setSelectedStaff((s) => s.filter((s) => s.id !== staff.id));
}, []);

const setTeamLead = useCallback((staff: StaffResult) => {
setSeletedTeamLead(staff.id)
const rearrangedList = getValues("addStaffIds").reduce<number[]>((acc, num, index) => {
if (num === staff.id && index !== 0) {
const setTeamLead = useCallback(
(staff: StaffResult) => {
const rearrangedList = getValues("addStaffIds").reduce<number[]>(
(acc, num, index) => {
if (num === staff.id && index !== 0) {
acc.splice(index, 1);
acc.unshift(num)
}
return acc;
}, getValues("addStaffIds"));
console.log(rearrangedList)
console.log(selectedStaff)

const rearrangedStaff = rearrangedList.map((id) => {
acc.unshift(num);
}
return acc;
},
getValues("addStaffIds")
);
console.log(rearrangedList);
console.log(selectedStaff);

const rearrangedStaff = rearrangedList.map((id) => {
return selectedStaff.find((staff) => staff.id === id);
});
console.log(rearrangedStaff)
setSelectedStaff(rearrangedStaff as StaffResult[]);
console.log(rearrangedStaff);
setSelectedStaff(rearrangedStaff as StaffResult[]);

setValue("addStaffIds", rearrangedList)
}, [addStaff, selectedStaff]);
setValue("addStaffIds", rearrangedList);
},
[addStaff, selectedStaff]
);

const clearSubsidiary = useCallback(() => {
if (defaultValues !== undefined) {
@@ -86,7 +101,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {

// Sync with form
useEffect(() => {
console.log(selectedStaff)
console.log(selectedStaff);
setValue(
"addStaffIds",
selectedStaff.map((s) => s.id)
@@ -94,7 +109,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
}, [selectedStaff, setValue]);

useEffect(() => {
console.log(selectedStaff)
console.log(selectedStaff);
}, [selectedStaff]);

const StaffPoolColumns = useMemo<Column<StaffResult>[]>(
@@ -107,7 +122,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
},
{ label: t("Staff Id"), name: "staffId" },
{ label: t("Staff Name"), name: "name" },
{ label: t("Current Position"), name: "currentPosition" },
{ label: t("Position"), name: "currentPosition" },
],
[addStaff, t]
);
@@ -122,7 +137,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
},
{ label: t("Staff Id"), name: "staffId" },
{ label: t("Staff Name"), name: "name" },
{ label: t("Current Position"), name: "currentPosition" },
{ label: t("Position"), name: "currentPosition" },
{
label: t("Team Lead"),
name: "action",
@@ -144,16 +159,16 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
}, []);

React.useEffect(() => {
// setFilteredStaff(
// initialStaffs.filter((s) => {
// const q = query.toLowerCase();
// // s.staffId.toLowerCase().includes(q)
// // const q = query.toLowerCase();
// // return s.name.toLowerCase().includes(q);
// // s.code.toString().includes(q) ||
// // (s.brNo != null && s.brNo.toLowerCase().includes(q))
// })
// );
setFilteredStaff(
initialStaffs.filter((i) => {
const q = query.toLowerCase();
return (
i.staffId.toLowerCase().includes(q) ||
i.name.toLowerCase().includes(q) ||
i.currentPosition.toLowerCase().includes(q)
);
})
);
}, [staff, query]);

const resetStaff = React.useCallback(() => {
@@ -161,8 +176,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
clearSubsidiary();
}, [clearQueryInput, clearSubsidiary]);

const formProps = useForm({
});
const formProps = useForm({});

// Tab related
const [tabIndex, setTabIndex] = React.useState(0);
@@ -170,7 +184,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
(_e, newValue) => {
setTabIndex(newValue);
},
[],
[]
);

return (
@@ -185,48 +199,48 @@ const StaffAllocation: React.FC<Props> = ({ allStaffs: staff }) => {
{t("staff")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6} display="flex" alignItems="center">
<Search sx={{ marginInlineEnd: 1 }} />
<TextField
variant="standard"
fullWidth
onChange={onQueryInputChange}
value={query}
placeholder={t("Search by subsidiary code, name or br no.")}
InputProps={{
endAdornment: query && (
<InputAdornment position="end">
<IconButton onClick={clearQueryInput}>
<Clear />
</IconButton>
</InputAdornment>
),
}}
/>
<Grid item xs={6} display="flex" alignItems="center">
<Search sx={{ marginInlineEnd: 1 }} />
<TextField
variant="standard"
fullWidth
onChange={onQueryInputChange}
value={query}
placeholder={t("Search by Staff Id, Name or Position.")}
InputProps={{
endAdornment: query && (
<InputAdornment position="end">
<IconButton onClick={clearQueryInput}>
<Clear />
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
</Grid>
</Grid>
<Tabs value={tabIndex} onChange={handleTabChange}>
<Tab label={t("Staff Pool")} />
<Tab
label={`${t("Allocated Staff")} (${selectedStaff.length})`}
/>
</Tabs>
<Box sx={{ marginInline: -3 }}>
{tabIndex === 0 && (
<SearchResults
noWrapper
items={differenceBy(filteredStaff, selectedStaff, "id")}
columns={StaffPoolColumns}
/>
)}
{tabIndex === 1 && (
<SearchResults
noWrapper
items={selectedStaff}
columns={allocatedStaffColumns}
<Tabs value={tabIndex} onChange={handleTabChange}>
<Tab label={t("Staff Pool")} />
<Tab
label={`${t("Allocated Staff")} (${selectedStaff.length})`}
/>
)}
</Box>
</Tabs>
<Box sx={{ marginInline: -3 }}>
{tabIndex === 0 && (
<SearchResults
noWrapper
items={differenceBy(filteredStaff, selectedStaff, "id")}
columns={StaffPoolColumns}
/>
)}
{tabIndex === 1 && (
<SearchResults
noWrapper
items={selectedStaff}
columns={allocatedStaffColumns}
/>
)}
</Box>
</Stack>
</CardContent>
</Card>


+ 4
- 0
src/components/NavigationContent/NavigationContent.tsx Näytä tiedosto

@@ -31,6 +31,8 @@ import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
import Logo from "../Logo";
import GroupIcon from '@mui/icons-material/Group';
import BusinessIcon from '@mui/icons-material/Business';
import ManageAccountsIcon from '@mui/icons-material/ManageAccounts';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';

interface NavigationItem {
icon: React.ReactNode;
@@ -117,10 +119,12 @@ const navigationItems: NavigationItem[] = [
{ icon: <BusinessIcon />, label: "Subsidiary", path: "/settings/subsidiary" },
{ icon: <Staff />, label: "Staff", path: "/settings/staff" },
{ icon: <Company />, label: "Company", path: "/settings/company" },
{ icon: <EmojiEventsIcon />, label: "Skill", path: "/settings/skill" },
{ icon: <Department />, label: "Department", path: "/settings/department" },
{ icon: <Position />, label: "Position", path: "/settings/position" },
{ icon: <Salary />, label: "Salary", path: "/settings/salary" },
{ icon: <Team />, label: "Team", path: "/settings/team" },
{ icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" },
],
},
];


+ 96
- 0
src/components/SkillSearch/SkillSearch.tsx Näytä tiedosto

@@ -0,0 +1,96 @@
"use client";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import SearchBox, { Criterion } from "../SearchBox/index";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults/index";
import EditNote from "@mui/icons-material/EditNote";
import DeleteIcon from "@mui/icons-material/Delete";
import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { SkillResult } from "@/app/api/skill";

interface Props {
skill: SkillResult[];
}

type SearchQuery = Partial<Omit<SkillResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const SkillSearch: React.FC<Props> = ({ skill }) => {
const { t } = useTranslation();
const [filteredStaff, setFilteredStaff] = useState(skill);
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Staff Name"),
paramName: "name",
type: "text",
},
],
[t]
);

const onSkillClick = useCallback(
(skill: SkillResult) => {
console.log(skill);
const id = skill.id;
// router.push(`/settings/skill/edit?id=${id}`);
},
[router, t]
);

const deleteClick = useCallback((skill: SkillResult) => {
// deleteDialog(async () => {
// await deleteStaff(skill.id);
// successDialog("Delete Success", t);
// setFilteredStaff((prev) => prev.filter((obj) => obj.id !== skill.id));
// }, t);
}, []);

const columns = useMemo<Column<SkillResult>[]>(
() => [
{
name: "action",
label: t("Actions"),
onClick: onSkillClick,
buttonIcon: <EditNote />,
},
{ name: "name", label: t("Name") },
{ name: "code", label: t("Code") },
{ name: "description", label: t("Description") },
{
name: "action",
label: t("Actions"),
onClick: deleteClick,
buttonIcon: <DeleteIcon />,
color: "error",
},
],
[t, onSkillClick, deleteClick]
);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
// setFilteredStaff(
// skill.filter(
// (s) =>
// s.skillId.toLowerCase().includes(query.skillId.toLowerCase()) &&
// s.name.toLowerCase().includes(query.name.toLowerCase())
// // (query.team === "All" || s.team === query.team) &&
// // (query.category === "All" || s.category === query.category) &&
// // (query.team === "All" || s.team === query.team),
// )
// );
}}
/>
<SearchResults<SkillResult> items={filteredStaff} columns={columns} />
</>
);
};

export default SkillSearch;

+ 40
- 0
src/components/SkillSearch/SkillSearchLoading.tsx Näytä tiedosto

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const SkillSearchLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default SkillSearchLoading;

+ 27
- 0
src/components/SkillSearch/SkillSearchWrapper.tsx Näytä tiedosto

@@ -0,0 +1,27 @@
import React from "react";
import SkillSearch from "./SkillSearch";
import SkillSearchLoading from "./SkillSearchLoading";
import { comboProp, fetchCompanyCombo } from "@/app/api/companys/actions";
import { fetchTeamCombo } from "@/app/api/team/actions";
import { fetchDepartmentCombo } from "@/app/api/departments/actions";
import { fetchPositionCombo } from "@/app/api/positions/actions";
import { fetchGradeCombo } from "@/app/api/grades/actions";
import { fetchSkillCombo } from "@/app/api/skill/actions";
import { fetchSalaryCombo } from "@/app/api/salarys/actions";
import { SkillResult, fetchSkill } from "@/app/api/skill";
// import { preloadStaff } from "@/app/api/staff";

interface SubComponents {
Loading: typeof SkillSearchLoading;
}

const SkillSearchWrapper: React.FC & SubComponents = async () => {
const skill = await fetchSkill()
console.log(skill);

return <SkillSearch skill={skill} />;
};

SkillSearchWrapper.Loading = SkillSearchLoading;

export default SkillSearchWrapper;

+ 1
- 0
src/components/SkillSearch/index.ts Näytä tiedosto

@@ -0,0 +1 @@
export { default } from "./SkillSearchWrapper";

+ 0
- 106
src/components/StaffSearch/ConfirmDeleteModal.tsx Näytä tiedosto

@@ -1,106 +0,0 @@
"use client";
import React, { useCallback, useMemo, useState } from "react";
import Button from "@mui/material/Button";
import { Card, Modal, Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import { Add } from "@mui/icons-material";
import Check from "@mui/icons-material/Check";
import Close from "@mui/icons-material/Close";
import { TSMS_BUTTON_THEME } from "@/theme/colorConst";
import { ThemeProvider } from "@emotion/react";

interface Props {
isOpen: boolean;
onConfirm: (data: any) => void;
onCancel: (data: any | null) => void;
// staff: StaffResult[];
}

const ConfirmModal: React.FC<Props> = ({ ...props }) => {
const { t } = useTranslation();
return (
<>
<Modal open={props.isOpen} onClose={props.onCancel}>
<Card
style={{
flex: 10,
marginBottom: "20px",
width: "auto",
minWidth: "400px",
minHeight: "200px",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}
>
<>
<Typography
variant="h5"
id="modal-title"
sx={{
flex: 1,
ml: 4,
mt: 2,
}}
>
{t("Confirm")}
</Typography>
<>
<Typography
variant="h6"
id="modal-title"
sx={{
flex: 1,
mt: 4,
justifyContent: "center",
textAlign: "center",
}}
>
{t("Are You Sure")}
</Typography>
</>
{/* <ThemeProvider theme={TSMS_BUTTON_THEME}> */}
<Stack direction="row">
<Button
variant="contained"
endIcon={<Check />}
sx={{
flex: 1,
ml: 5,
mr: 2,
mt: 4,
justifyContent: "space-between",
}}
onClick={props.onConfirm}
// LinkComponent={Link}
// href="/settings/department/new"
>
Proceed
</Button>
<Button
variant="contained"
startIcon={<Close />}
sx={{
flex: 1,
mr: 5,
mt: 4,
justifyContent: "space-between",
}}
color="warning"
onClick={props.onCancel}
// LinkComponent={Link}
// href="/settings/department/new"
>
Cancel
</Button>
</Stack>
{/* </ThemeProvider> */}
</>
</Card>
</Modal>
</>
);
};

export default ConfirmModal;

+ 28
- 51
src/components/StaffSearch/StaffSearch.tsx Näytä tiedosto

@@ -5,15 +5,11 @@ import SearchBox, { Criterion } from "../SearchBox/index";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults/index";
import EditNote from "@mui/icons-material/EditNote";
import DeleteIcon from '@mui/icons-material/Delete';
import ConfirmModal from "./ConfirmDeleteModal";
import DeleteIcon from "@mui/icons-material/Delete";
import { deleteStaff } from "@/app/api/staff/actions";
import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";

interface combo {
id: any;
label: string;
}
interface Props {
staff: StaffResult[];
}
@@ -24,8 +20,6 @@ type SearchParamNames = keyof SearchQuery;
const StaffSearch: React.FC<Props> = ({ staff }) => {
const { t } = useTranslation();
const [filteredStaff, setFilteredStaff] = useState(staff);
const [data, setData] = useState<StaffResult>();
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
@@ -41,10 +35,10 @@ const StaffSearch: React.FC<Props> = ({ staff }) => {
paramName: "name",
type: "text",
},
{
label: t("Staff ID"),
paramName: "staffId",
type: "text"
{
label: t("Staff ID"),
paramName: "staffId",
type: "text",
},
{
label: t("Grade"),
@@ -59,39 +53,26 @@ const StaffSearch: React.FC<Props> = ({ staff }) => {
options: ["pos1", "CEO"],
},
],
[t],
[t]
);

const onStaffClick = useCallback((staff: StaffResult) => {
console.log(staff);
const id = staff.id
router.push(`/settings/staff/edit?id=${id}`);
}, [router, t]);

const deleteClick = (staff: StaffResult) => {
console.log(staff);
setData(staff)
setIsOpen(!isOpen)
};

const onConfirm = useCallback(async (staff: StaffResult) => {
console.log(staff);
if (data)
await deleteStaff(data)
setIsOpen(false)
window.location.reload;
}, [deleteStaff, data]);
const onStaffClick = useCallback(
(staff: StaffResult) => {
console.log(staff);
const id = staff.id;
router.push(`/settings/staff/edit?id=${id}`);
},
[router, t]
);

const onCancel = useCallback((staff: StaffResult) => {
console.log(staff);
setIsOpen(false)
const deleteClick = useCallback((staff: StaffResult) => {
deleteDialog(async () => {
await deleteStaff(staff.id);
successDialog("Delete Success", t);
setFilteredStaff((prev) => prev.filter((obj) => obj.id !== staff.id));
}, t);
}, []);

// useEffect(() => {
// console.log("id");
// console.log(id);
// }, [id]);

const columns = useMemo<Column<StaffResult>[]>(
() => [
{
@@ -110,34 +91,30 @@ const StaffSearch: React.FC<Props> = ({ staff }) => {
label: t("Actions"),
onClick: deleteClick,
buttonIcon: <DeleteIcon />,
color: "error",
},
],
[t, onStaffClick, deleteClick],
[t, onStaffClick, deleteClick]
);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
onSearch={(query) => {
setFilteredStaff(
staff.filter(
(s) =>
s.staffId.toLowerCase().includes(query.staffId.toLowerCase()) &&
s.name.toLowerCase().includes(query.name.toLowerCase())
(s) =>
s.staffId.toLowerCase().includes(query.staffId.toLowerCase()) &&
s.name.toLowerCase().includes(query.name.toLowerCase())
// (query.team === "All" || s.team === query.team) &&
// (query.category === "All" || s.category === query.category) &&
// (query.team === "All" || s.team === query.team),
)
)
);
}}
/>
<SearchResults<StaffResult> items={filteredStaff} columns={columns} />
<ConfirmModal
isOpen={isOpen}
onConfirm={onConfirm}
onCancel={onCancel}
/>
</>
);
};


+ 0
- 105
src/components/TeamSearch/ConfirmDeleteModal.tsx Näytä tiedosto

@@ -1,105 +0,0 @@
"use client";
import React, { useCallback, useMemo, useState } from "react";
import Button from "@mui/material/Button";
import { Card, Modal, Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import { Add } from "@mui/icons-material";
import Check from "@mui/icons-material/Check";
import Close from "@mui/icons-material/Close";
import { TSMS_BUTTON_THEME } from "@/theme/colorConst";
import { ThemeProvider } from "@emotion/react";

interface Props {
isOpen: boolean;
onConfirm: (data: any) => void;
onCancel: (data: any | null) => void;
}

const ConfirmModal: React.FC<Props> = ({ ...props }) => {
const { t } = useTranslation();
return (
<>
<Modal open={props.isOpen} onClose={props.onCancel}>
<Card
style={{
flex: 10,
marginBottom: "20px",
width: "auto",
minWidth: "400px",
minHeight: "200px",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}
>
<>
<Typography
variant="h5"
id="modal-title"
sx={{
flex: 1,
ml: 4,
mt: 2,
}}
>
{t("Confirm")}
</Typography>
<>
<Typography
variant="h6"
id="modal-title"
sx={{
flex: 1,
mt: 4,
justifyContent: "center",
textAlign: "center",
}}
>
{t("Are You Sure")}
</Typography>
</>
{/* <ThemeProvider theme={TSMS_BUTTON_THEME}> */}
<Stack direction="row">
<Button
variant="contained"
endIcon={<Check />}
sx={{
flex: 1,
ml: 5,
mr: 2,
mt: 4,
justifyContent: "space-between",
}}
onClick={props.onConfirm}
// LinkComponent={Link}
// href="/settings/department/new"
>
Proceed
</Button>
<Button
variant="contained"
startIcon={<Close />}
sx={{
flex: 1,
mr: 5,
mt: 4,
justifyContent: "space-between",
}}
color="warning"
onClick={props.onCancel}
// LinkComponent={Link}
// href="/settings/department/new"
>
Cancel
</Button>
</Stack>
{/* </ThemeProvider> */}
</>
</Card>
</Modal>
</>
);
};

export default ConfirmModal;

+ 74
- 95
src/components/TeamSearch/TeamSearch.tsx Näytä tiedosto

@@ -6,12 +6,10 @@ import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults/index";
import EditNote from "@mui/icons-material/EditNote";
import DeleteIcon from '@mui/icons-material/Delete';
import { deleteStaff } from "@/app/api/staff/actions";
import DeleteIcon from "@mui/icons-material/Delete";
import { useRouter } from "next/navigation";
import ConfirmModal from "./ConfirmDeleteModal";
import { deleteTeam } from "@/app/api/team/actions";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";

interface Props {
team: TeamResult[];
@@ -20,109 +18,90 @@ type SearchQuery = Partial<Omit<TeamResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const TeamSearch: React.FC<Props> = ({ team }) => {
const { t } = useTranslation();
const [filteredTeam, setFilteredTeam] = useState(team);
const [data, setData] = useState<TeamResult>();
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Team Name"),
paramName: "name",
type: "text",
},
{
label: t("Team Code"),
paramName: "code",
type: "text",
},
{
label: t("Team Description"),
paramName: "description",
type: "text",
},
],
[t],
);
const { t } = useTranslation();
const [filteredTeam, setFilteredTeam] = useState(team);
const router = useRouter();

const onTeamClick = useCallback((team: TeamResult) => {
console.log(team);
const id = team.id
router.push(`/settings/team/edit?id=${id}`);
}, [router, t]);

// const onDeleteClick = useCallback((team: TeamResult) => {
// console.log(team);
// deleteTeam
const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Team Name"),
paramName: "name",
type: "text",
},
{
label: t("Team Code"),
paramName: "code",
type: "text",
},
{
label: t("Team Description"),
paramName: "description",
type: "text",
},
],
[t]
);

// }, [router, t]);
const onTeamClick = useCallback(
(team: TeamResult) => {
console.log(team);
const id = team.id;
router.push(`/settings/team/edit?id=${id}`);
},
[router, t]
);

const onDeleteClick = (team: TeamResult) => {
console.log(team);
setData(team)
setIsOpen(!isOpen)
};
const onDeleteClick = useCallback((team: TeamResult) => {
deleteDialog(async () => {
await deleteTeam(team.id);

const onConfirm = useCallback(async (team: TeamResult) => {
console.log(team);
if (data)
await deleteTeam(data)
setIsOpen(false)
window.location.reload;
}, [deleteTeam, data]);
successDialog("Delete Success", t);

const onCancel = useCallback(() => {
setIsOpen(false)
}, []);
setFilteredTeam((prev) => prev.filter((obj) => obj.id !== team.id));
}, t);
}, []);

const columns = useMemo<Column<TeamResult>[]>(
() => [
{
name: "action",
label: t("Edit"),
onClick: onTeamClick,
buttonIcon: <EditNote />,
},
{ name: "name", label: t("Name") },
{ name: "code", label: t("Code") },
{ name: "description", label: t("description") },
{
name: "action",
label: t("Delete"),
onClick: onDeleteClick,
buttonIcon: <DeleteIcon />,
},
],
[t],
);
const columns = useMemo<Column<TeamResult>[]>(
() => [
{
name: "action",
label: t("Edit"),
onClick: onTeamClick,
buttonIcon: <EditNote />,
},
{ name: "name", label: t("Name") },
{ name: "code", label: t("Code") },
{ name: "description", label: t("description") },
{ name: "staffName", label: t("TeamLead") },
{
name: "action",
label: t("Delete"),
onClick: onDeleteClick,
buttonIcon: <DeleteIcon />,
color: "error"
},
],
[t]
);

return (
<>
<SearchBox
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
// setFilteredStaff(
// staff.filter(
// (s) =>
// s.staffId.toLowerCase().includes(query.staffId.toLowerCase()) &&
// s.name.toLowerCase().includes(query.name.toLowerCase())
// // (query.team === "All" || s.team === query.team) &&
// // (query.category === "All" || s.category === query.category) &&
// // (query.team === "All" || s.team === query.team),
// )
// )
onSearch={(query) => {
setFilteredTeam(
team.filter(
(t) =>
t.name.toLowerCase().includes(query.name.toLowerCase()) &&
t.code.toLowerCase().includes(query.code.toLowerCase()) &&
t.description.toLowerCase().includes(query.description.toLowerCase())
)
)
}}
/>
<SearchResults<TeamResult> items={filteredTeam} columns={columns} />
<ConfirmModal
isOpen={isOpen}
onConfirm={onConfirm}
onCancel={onCancel}
/>

</>
</>
);
};
export default TeamSearch;

+ 98
- 0
src/components/UserSearch/UserSearch.tsx Näytä tiedosto

@@ -0,0 +1,98 @@
"use client";

import SearchBox, { Criterion } from "../SearchBox";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults/index";
import EditNote from "@mui/icons-material/EditNote";
import DeleteIcon from "@mui/icons-material/Delete";
import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { UserResult } from "@/app/api/user";
import { deleteUser } from "@/app/api/user/actions";

interface Props {
users: UserResult[];
}
type SearchQuery = Partial<Omit<UserResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const UserSearch: React.FC<Props> = ({ users }) => {
const { t } = useTranslation();
const [filteredUser, setFilteredUser] = useState(users);
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("User Name"),
paramName: "title",
type: "text",
},
],
[t]
);

const onUserClick = useCallback(
(users: UserResult) => {
console.log(users);
router.push(`/settings/user/edit?id=${users.id}`)
},
[router, t]
);

const onDeleteClick = useCallback((users: UserResult) => {
deleteDialog(async () => {
await deleteUser(users.id);

successDialog("Delete Success", t);

setFilteredUser((prev) => prev.filter((obj) => obj.id !== users.id));
}, t);
}, []);

const columns = useMemo<Column<UserResult>[]>(
() => [
{
name: "action",
label: t("Edit"),
onClick: onUserClick,
buttonIcon: <EditNote />,
},
{ name: "name", label: t("UserName") },
{ name: "fullName", label: t("FullName") },
{ name: "title", label: t("Title") },
{ name: "department", label: t("Department") },
{ name: "email", label: t("Email") },
{ name: "phone1", label: t("Phone") },
{
name: "action",
label: t("Delete"),
onClick: onDeleteClick,
buttonIcon: <DeleteIcon />,
color: "error"
},
],
[t]
);

return (
<>
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
// setFilteredUser(
// users.filter(
// (t) =>
// t.name.toLowerCase().includes(query.name.toLowerCase()) &&
// t.code.toLowerCase().includes(query.code.toLowerCase()) &&
// t.description.toLowerCase().includes(query.description.toLowerCase())
// )
// )
}}
/>
<SearchResults<UserResult> items={filteredUser} columns={columns} />
</>
);
};
export default UserSearch;

+ 40
- 0
src/components/UserSearch/UserSearchLoading.tsx Näytä tiedosto

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const UserSearchLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default UserSearchLoading;

+ 19
- 0
src/components/UserSearch/UserSearchWrapper.tsx Näytä tiedosto

@@ -0,0 +1,19 @@
import React from "react";
import UserSearch from "./UserSearch";
import UserSearchLoading from "./UserSearchLoading";
import { UserResult, fetchUser } from "@/app/api/user";

interface SubComponents {
Loading: typeof UserSearchLoading;
}

const UserSearchWrapper: React.FC & SubComponents = async () => {
const users = await fetchUser()
console.log(users);

return <UserSearch users={users} />;
};

UserSearchWrapper.Loading = UserSearchLoading;

export default UserSearchWrapper;

+ 1
- 0
src/components/UserSearch/index.ts Näytä tiedosto

@@ -0,0 +1 @@
export { default } from "./UserSearchWrapper";

Ladataan…
Peruuta
Tallenna