Przeglądaj źródła

1. Edit Poistion (Can view, but no update yet, Backend not ready)

2. Invoice Search page
3. Create Invoice page
tags/Baseline_30082024_FRONTEND_UAT
MSI\2Fi 1 rok temu
rodzic
commit
579362758f
18 zmienionych plików z 745 dodań i 38 usunięć
  1. +6
    -4
      src/app/(main)/invoice/new/page.tsx
  2. +2
    -2
      src/app/(main)/invoice/page.tsx
  3. +25
    -0
      src/app/(main)/settings/position/edit/page.tsx
  4. +32
    -22
      src/app/api/invoices/actions.ts
  5. +9
    -0
      src/app/api/invoices/index.ts
  6. +22
    -1
      src/app/api/positions/actions.ts
  7. +142
    -0
      src/components/CreateInvoice/CreateInvoice.tsx
  8. +11
    -0
      src/components/CreateInvoice/CreateInvoiceWrapper.tsx
  9. +119
    -0
      src/components/CreateInvoice/InvoiceDetails.tsx
  10. +102
    -0
      src/components/CreateInvoice/ProjectDetails.tsx
  11. +1
    -0
      src/components/CreateInvoice/index.ts
  12. +144
    -0
      src/components/EditPosition/EditPosition.tsx
  13. +18
    -0
      src/components/EditPosition/EditPositionWrapper.tsx
  14. +87
    -0
      src/components/EditPosition/PositionDetails.tsx
  15. +1
    -0
      src/components/EditPosition/index.ts
  16. +17
    -4
      src/components/InvoiceSearch/InvoiceSearch.tsx
  17. +0
    -2
      src/components/InvoiceSearch/InvoiceSearchWrapper.tsx
  18. +7
    -3
      src/components/PositionSearch/PositionSearch.tsx

+ 6
- 4
src/app/(main)/invoice/new/page.tsx Wyświetl plik

@@ -1,10 +1,11 @@
import { Metadata } from "next";
import { getServerI18n } from "@/i18n";
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 Link from "next/link";
import CreateInvoice from "@/components/CreateInvoice";

export const metadata: Metadata = {
title: "Create Invoice",
@@ -15,9 +16,10 @@ const Invoice: React.FC = async () => {

return (
<>
<Typography variant="h4" marginInlineEnd={2}>
{t("Create Invoice")}
</Typography>
<Typography variant="h4">{t("Create Invoice")}</Typography>
<I18nProvider namespaces={["invoice"]}>
<CreateInvoice />
</I18nProvider>
</>
)
};


+ 2
- 2
src/app/(main)/invoice/page.tsx Wyświetl plik

@@ -26,14 +26,14 @@ const Invoice: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}>
{t("Invoice")}
</Typography>
<Button
{/* <Button
variant="contained"
startIcon={<Add />}
LinkComponent={Link}
href="/invoice/new"
>
{t("Create Invoice")}
</Button>
</Button> */}
</Stack>
<Suspense fallback={<InvoiceSearch.Loading />}>
<InvoiceSearch />


+ 25
- 0
src/app/(main)/settings/position/edit/page.tsx Wyświetl plik

@@ -0,0 +1,25 @@
import EditPosition from "@/components/EditPosition";
import { I18nProvider, getServerI18n } from "@/i18n";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";

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

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

// Preload necessary dependencies

return (
<>
<Typography variant="h4">{t("Edit Position")}</Typography>
<I18nProvider namespaces={["positions"]}>
<EditPosition />
</I18nProvider>
</>
);
};

export default Positions;

+ 32
- 22
src/app/api/invoices/actions.ts Wyświetl plik

@@ -4,32 +4,42 @@ import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";


export interface comboProp {
id: any;
label: string;
export interface InvoiceResult {
id: number;
projectCode: string;
projectName: string;
stage: String;
comingPaymentMileStone: String;
paymentMilestoneDate: String;
resourceUsage: number;
unbilledHours: number;
reminder: String;
}

export interface combo {
records: comboProp[];
export interface CreateInvoiceInputs {
id: number;
amount: number;
billHours: number;
}
export interface CreateDepartmentInputs {
departmentCode: string;
departmentName: string;
description: string;

export interface InvoiceInformation{
id: number;
client: string;
address: string;
attention: string;
invoiceDate: string;
dueDate: string;
projectRefNo: string;
}

export const saveDepartment = async (data: CreateDepartmentInputs) => {
return serverFetchJson(`${BASE_API_URL}/departments/new`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
export const fetchProjectInvoiceById = cache(async (id: number) => {
return serverFetchJson<InvoiceResult[]>(`${BASE_API_URL}/invoices/getProjectDetailById?id=${id}`, {
next: { tags: ["projectDetailById"] },
});
};
})


export const fetchDepartmentCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/departments/combo`, {
next: { tags: ["department"] },
});
});
export const fetchInvoiceInfoById = cache(async (id: number) => {
return serverFetchJson<InvoiceInformation[]>(`${BASE_API_URL}/invoices/getInvoiceInfoById?id=${id}`, {
next: { tags: ["invoiceInfoById"] },
});
})

+ 9
- 0
src/app/api/invoices/index.ts Wyświetl plik

@@ -15,6 +15,15 @@ export interface InvoiceResult {
reminder: String;
}

export interface InvoiceInformatio{
id: number;
address: string;
attention: string;
invoiceDate: string;
dueDate: string;
projectRefNo: string;
}

export const preloadInvoices = () => {
fetchInvoices();
};


+ 22
- 1
src/app/api/positions/actions.ts Wyświetl plik

@@ -3,6 +3,7 @@
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import { PositionResult } from ".";

export interface comboProp {
id: any;
@@ -19,6 +20,13 @@ export interface CreatePositionInputs {
description: string;
}

export interface EditPositionInputs {
id: number;
positionCode: string;
positionName: string;
description: string;
}

export const savePosition = async (data: CreatePositionInputs) => {
return serverFetchJson(`${BASE_API_URL}/positions/new`, {
method: "POST",
@@ -27,9 +35,22 @@ export const savePosition = async (data: CreatePositionInputs) => {
});
};

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

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

export const fetchPositionDetails = cache(async (id: number) => {
return serverFetchJson<PositionResult[]>(`${BASE_API_URL}/positions/${id}`, {
next: { tags: ["positionsDetails"] },
});
});

+ 142
- 0
src/components/CreateInvoice/CreateInvoice.tsx Wyświetl plik

@@ -0,0 +1,142 @@
"use client";
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 Print from '@mui/icons-material/Print';
// import { CreateInvoiceInputs, saveInvoice } from "@/app/api/companys/actions";
import { useRouter } from "next/navigation";
import React, { useCallback, useState, useLayoutEffect } from "react";
import { useTranslation } from "react-i18next";
import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { useSearchParams } from 'next/navigation'
import { InvoiceResult } from "@/app/api/invoices";
import { InvoiceInformation, fetchInvoiceInfoById, fetchProjectInvoiceById } from "@/app/api/invoices/actions";
import InvoiceDetails from "./InvoiceDetails";
import ProjectDetails from "./ProjectDetails";

const CreateInvoice: React.FC = ({
}) => {
const { t } = useTranslation();
const searchParams = useSearchParams()
const router = useRouter()
const [projectDetail, setProjectDetail] = useState<InvoiceResult>()
const [invoiceDetail, setInvoiceDetail] = useState<InvoiceInformation>()
const [serverError, setServerError] = useState("");

const fetchProjectDetails = async () =>{
const projectId = searchParams.get("id")
try{
if (projectId !== null && parseInt(projectId) > 0) {
const projectDetail = await fetchProjectInvoiceById(parseInt(projectId))
console.log(projectDetail)
setProjectDetail(projectDetail[0])
}
} catch (error){
console.log(error)
setServerError(t("An error has occurred. Please try again later."));
}
}

const fetchInvoiceDetails = async () =>{
const projectId = searchParams.get("id")
try{
if (projectId !== null && parseInt(projectId) > 0) {
const invoiceInfo = await fetchInvoiceInfoById(parseInt(projectId))
console.log(invoiceInfo)
setInvoiceDetail(invoiceInfo[0])
}
} catch (error){
console.log(error)
setServerError(t("An error has occurred. Please try again later."));
}
}

useLayoutEffect(() => {
fetchProjectDetails()
fetchInvoiceDetails()
}, [])

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

const handlePrintout = () => {
console.log("Printing in Progress")
}

const onSubmit = useCallback<SubmitHandler<InvoiceResult>>(
async (data) => {
try {
console.log(data);
setServerError("");
// console.log(JSON.stringify(data));
// await saveCompany(data)
// router.replace("/invoices");
} catch (e) {
setServerError(t("An error has occurred. Please try again later."));
}
},
[router, t],
);

const onSubmitError = useCallback<SubmitErrorHandler<InvoiceResult>>(
(errors) => {
console.log(errors)
},
[],
);

const formProps = useForm<InvoiceResult>({
defaultValues: {
},
});

const errors = formProps.formState.errors;

return(
<FormProvider {...formProps}>
<Stack
spacing={2}
component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
{
projectDetail && <ProjectDetails projectDetails={projectDetail}/>
}
{
invoiceDetail && <InvoiceDetails invoiceinfo={invoiceDetail}/>
}
<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">
{t("Confirm")}
</Button>
<Button
variant="contained"
color="secondary"
startIcon={<Print />}
onClick={handlePrintout}
>
{t("Generate Print Out")}
</Button>
</Stack>
</Stack>
</FormProvider>
)
}

export default CreateInvoice;

+ 11
- 0
src/components/CreateInvoice/CreateInvoiceWrapper.tsx Wyświetl plik

@@ -0,0 +1,11 @@
import CreateInvoice from "./CreateInvoice";

const CreateInvoiceWrapper: React.FC = async () => {

return (
<CreateInvoice
/>
)
}

export default CreateInvoiceWrapper;

+ 119
- 0
src/components/CreateInvoice/InvoiceDetails.tsx Wyświetl plik

@@ -0,0 +1,119 @@
"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 FormControl from "@mui/material/FormControl";
import Grid from "@mui/material/Grid";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
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, UseFormRegister, useFormContext } from "react-hook-form";
import { CreateInvoiceInputs } from "@/app/api/invoices/actions";
import { TimePicker } from "@mui/x-date-pickers";
import dayjs from 'dayjs';
import { InvoiceInformation } from "@/app/api/invoices/actions";

interface Props {
invoiceinfo: InvoiceInformation
}

const InvoiceDetails: React.FC<Props> = ({
invoiceinfo,
}) => {
const { t } = useTranslation();
const {
register,
formState: { errors },
control,
setValue,
getValues,
} = useFormContext<CreateInvoiceInputs>();
console.log(invoiceinfo)

return (
<Card>
<CardContent component={Stack} spacing={4}>
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Invoice Information")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Client")}
fullWidth
disabled
defaultValue={invoiceinfo.client}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Address")}
fullWidth
disabled
defaultValue={invoiceinfo.address}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Attention")}
fullWidth
disabled
defaultValue={invoiceinfo.attention}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Invoice Date")}
fullWidth
defaultValue={invoiceinfo.invoiceDate}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Due Date")}
fullWidth
defaultValue={invoiceinfo.dueDate}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Project ref no.")}
fullWidth
disabled
defaultValue={invoiceinfo.projectRefNo}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Amount (HKD)")}
fullWidth
{...register("amount", {
required: "Amount required!",
})}
error={Boolean(errors.amount)}
/>
</Grid>
</Grid>
</Box>
{/* <CardActions sx={{ justifyContent: "flex-end" }}>
<Button variant="text" startIcon={<RestartAlt />}>
{t("Reset")}
</Button>
</CardActions> */}
</CardContent>
</Card>
);
};

export default InvoiceDetails;

+ 102
- 0
src/components/CreateInvoice/ProjectDetails.tsx Wyświetl plik

@@ -0,0 +1,102 @@
"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 FormControl from "@mui/material/FormControl";
import Grid from "@mui/material/Grid";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import { InvoiceResult } from "@/app/api/invoices";
import { useFormContext } from "react-hook-form";

interface Props {
projectDetails: InvoiceResult
}

const ProjectDetails: React.FC<Props> = ({
projectDetails,
}) => {
const { t } = useTranslation();
const {
register,
formState: { errors },
control,
setValue,
getValues,
} = useFormContext<InvoiceResult>();

return (
<Card>
<CardContent component={Stack} spacing={4}>
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Project Details")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Poject code")}
fullWidth
disabled
defaultValue={projectDetails.projectCode}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Project name")}
fullWidth
disabled
defaultValue={projectDetails.projectName}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Stage")}
fullWidth
disabled
defaultValue={projectDetails.stage}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Payment milestone date")}
fullWidth
disabled
defaultValue={projectDetails.paymentMilestoneDate}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Coming payment milestone")}
fullWidth
disabled
defaultValue={projectDetails.comingPaymentMileStone}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Unbilled Hours")}
fullWidth
disabled
defaultValue={projectDetails.unbilledHours}
/>
</Grid>
</Grid>
</Box>
{/* <CardActions sx={{ justifyContent: "flex-end" }}>
<Button variant="text" startIcon={<RestartAlt />}>
{t("Reset")}
</Button>
</CardActions> */}
</CardContent>
</Card>
);
};

export default ProjectDetails;

+ 1
- 0
src/components/CreateInvoice/index.ts Wyświetl plik

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

+ 144
- 0
src/components/EditPosition/EditPosition.tsx Wyświetl plik

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

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, useLayoutEffect, useEffect } 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 { EditPositionInputs, editPosition, fetchPositionDetails, savePosition } from "@/app/api/positions/actions";
import { Error } from "@mui/icons-material";
import { ProjectCategory } from "@/app/api/projects";
import { Typography } from "@mui/material";
import PositionDetails from "./PositionDetails";
import { useSearchParams } from 'next/navigation'
import { PositionResult } from "@/app/api/positions";


const EditPosition: React.FC = ({
// allTasks,
// projectCategories,
// taskTemplates,
// teamLeads,
}) => {
const [serverError, setServerError] = useState("");
const { t } = useTranslation();
const searchParams = useSearchParams()
const router = useRouter();

const [positionDetails, setPositionDetails] = useState<EditPositionInputs>()
const positionId = searchParams.get("id")

const fetchPositionDetail = async () =>{
console.log(positionId)
try{
if (positionId !== null && parseInt(positionId) > 0) {
const postionDetails = await fetchPositionDetails(parseInt(positionId))
const updatedArray: EditPositionInputs[] = postionDetails.map((obj) => {
return {
id: obj.id,
positionCode: obj.code,
positionName: obj.name,
description: obj.description
};
});

setPositionDetails(updatedArray[0])
}
} catch (error){
console.log(error)
setServerError(t("An error has occurred. Please try again later."));
}
}

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

const onSubmit = useCallback<SubmitHandler<EditPositionInputs>>(
async (data) => {
try {
if (positionId !== null && parseInt(positionId) > 0) {
console.log(data);
setServerError("");
// console.log(JSON.stringify(data));
data.id = parseInt(positionId)
console.log(data);
await editPosition(data)
router.replace("/settings/position");
}
} catch (e) {
setServerError(t("An error has occurred. Please try again later."));
}
},
[router, t],
);

const onSubmitError = useCallback<SubmitErrorHandler<EditPositionInputs>>(
(errors) => {
console.log(errors)
},
[],
);

const formProps = useForm<EditPositionInputs>({
// defaultValues: {
// positionCode: positionDetails ? positionDetails.code : "AAA",
// positionName: positionDetails ? positionDetails.name : "BBB",
// description: positionDetails ? positionDetails.description : "CCC",
// },
});

const errors = formProps.formState.errors;

useEffect(() => {
fetchPositionDetail()
}, [])

useEffect(() => {
if (positionDetails !== null && positionDetails !== undefined) {
console.log(positionDetails)
formProps.reset(positionDetails)
}
}, [positionDetails])

return (
<FormProvider {...formProps}>
<Stack
spacing={2}
component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
>
{
positionDetails && <PositionDetails positionDetails={positionDetails}/>
}
<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">
{t("Confirm")}
</Button>
</Stack>
</Stack>
</FormProvider>
);
};

export default EditPosition;

+ 18
- 0
src/components/EditPosition/EditPositionWrapper.tsx Wyświetl plik

@@ -0,0 +1,18 @@
import EditPosition from "./EditPosition";

const EditPositionWrapper: React.FC = async () => {
// const [tasks, taskTemplates, PositionCategories, teamLeads] =
// await Promise.all([
// fetchAllTasks(),
// fetchTaskTemplates(),
// fetchPositionCategories(),
// fetchTeamLeads(),
// ]);

return (
<EditPosition
/>
);
};

export default EditPositionWrapper;

+ 87
- 0
src/components/EditPosition/PositionDetails.tsx Wyświetl plik

@@ -0,0 +1,87 @@
"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 FormControl from "@mui/material/FormControl";
import Grid from "@mui/material/Grid";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
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 { EditPositionInputs } from "@/app/api/positions/actions";
import { PositionResult } from "@/app/api/positions";
import { useEffect } from "react";

interface Props {
positionDetails: EditPositionInputs
}
const PositionDetails: React.FC<Props> = ({
positionDetails,
}) => {
const { t } = useTranslation();
const {
register,
formState: { errors },
control,
setValue,
} = useFormContext<EditPositionInputs>();

return (
<Card>
<CardContent component={Stack} spacing={4}>
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Position Details")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Position Code")}
fullWidth
{...register("positionCode", {
required: "Position code required!",
})}
error={Boolean(errors.positionCode)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Position Name")}
fullWidth
{...register("positionName", {
required: "Position name required!",
})}
error={Boolean(errors.positionName)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Position Description")}
fullWidth
{...register("description", {
required: "Please enter a description",
})}
error={Boolean(errors.description)}
/>
</Grid>
</Grid>
</Box>
{/* <CardActions sx={{ justifyContent: "flex-end" }}>
<Button variant="text" startIcon={<RestartAlt />}>
{t("Reset")}
</Button>
</CardActions> */}
</CardContent>
</Card>
);
};

export default PositionDetails;

+ 1
- 0
src/components/EditPosition/index.ts Wyświetl plik

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

+ 17
- 4
src/components/InvoiceSearch/InvoiceSearch.tsx Wyświetl plik

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults";
import EditNote from "@mui/icons-material/EditNote";
import { InvoiceResult } from "@/app/api/invoices";
import { useRouter } from "next/navigation";

interface Props {
invoices: InvoiceResult[];
@@ -16,6 +17,7 @@ type SearchParamNames = keyof SearchQuery;

const InvoiceSearch: React.FC<Props> = ({ invoices }) => {
const { t } = useTranslation("invoices");
const router = useRouter();

const [filteredInvoices, setFilteredInvoices] = useState(invoices);

@@ -24,8 +26,18 @@ const InvoiceSearch: React.FC<Props> = ({ invoices }) => {
{ label: t("Project code"), paramName: "projectCode", type: "text" },
{ label: t("Project name"), paramName: "projectName", type: "text" },
// { label: t("Stage"), paramName: "stage", type: "text" },
{ label: t("Coming payment milestone"), paramName: "comingPaymentMileStone", type: "text" },
{ label: t("Payment date"), paramName: "paymentMilestoneDate", type: "text" },
{
label: t("Coming payment milestone from"),
label2: t("Coming payment milestone to"),
paramName: "comingPaymentMileStone",
type: "dateRange"
},
{
label: t("Payment date from"),
label2: t("Payment date to"),
paramName: "paymentMilestoneDate",
type: "dateRange"
},
// { label: t("Resource utilization %"), paramName: "resourceUsage", type: "text" },
// { label: t("Unbilled hours"), paramName: "unbilledHours", type: "text" },
// { label: t("Reminder to issue invoice"), paramName: "reminder", type: "text" },
@@ -39,7 +51,8 @@ const InvoiceSearch: React.FC<Props> = ({ invoices }) => {

const onProjectClick = useCallback((project: InvoiceResult) => {
console.log(project);
}, []);
router.push(`/invoice/new?id=${project.id}`)
}, [router, t]);

const columns = useMemo<Column<InvoiceResult>[]>(
() => [
@@ -66,12 +79,12 @@ const InvoiceSearch: React.FC<Props> = ({ invoices }) => {
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
console.log(query)
setFilteredInvoices(
invoices.filter(
(d) =>
d.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) &&
d.projectName.toLowerCase().includes(query.projectName.toLowerCase()) &&
d.stage.toLowerCase().includes(query.stage.toLowerCase()) &&
{/*(query.client === "All" || p.client === query.client) &&
(query.category === "All" || p.category === query.category) &&
(query.team === "All" || p.team === query.team), **/}


+ 0
- 2
src/components/InvoiceSearch/InvoiceSearchWrapper.tsx Wyświetl plik

@@ -2,7 +2,6 @@
import React from "react";
import InvoiceSearch from "./InvoiceSearch";
import InvoiceSearchLoading from "./InvoiceSearchLoading";
// For Later use
import { fetchInvoices } from "@/app/api/invoices";

interface SubComponents {
@@ -10,7 +9,6 @@ interface SubComponents {
}

const InvoiceSearchWrapper: React.FC & SubComponents = async () => {
// For Later use
const Invoices = await fetchInvoices();

return <InvoiceSearch invoices={Invoices} />;


+ 7
- 3
src/components/PositionSearch/PositionSearch.tsx Wyświetl plik

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults";
import EditNote from "@mui/icons-material/EditNote";
import { PositionResult } from "@/app/api/positions";
import { useRouter } from "next/navigation";

interface Props {
positions: PositionResult[];
@@ -16,6 +17,7 @@ type SearchParamNames = keyof SearchQuery;

const PositionSearch: React.FC<Props> = ({ positions }) => {
const { t } = useTranslation("positions");
const router = useRouter();

const [filteredPositions, setFilteredPositions] = useState(positions);

@@ -32,8 +34,10 @@ const PositionSearch: React.FC<Props> = ({ positions }) => {
setFilteredPositions(positions);
}, [positions]);

const onProjectClick = useCallback((project: PositionResult) => {
const onPositionClick = useCallback((project: PositionResult) => {
console.log(project);
const id = project.id
router.push(`/settings/position/edit?id=${id}`);
}, []);

const columns = useMemo<Column<PositionResult>[]>(
@@ -41,14 +45,14 @@ const PositionSearch: React.FC<Props> = ({ positions }) => {
{
name: "id",
label: t("Details"),
onClick: onProjectClick,
onClick: onPositionClick,
buttonIcon: <EditNote />,
},
{ name: "code", label: t("Position Code") },
{ name: "name", label: t("Position Name") },
{ name: "description", label: t("Position Description") },
],
[t, onProjectClick],
[t, onPositionClick],
);

return (


Ładowanie…
Anuluj
Zapisz