Ticket edit updates

This commit is contained in:
Darren Clarke 2024-08-07 15:25:53 +02:00
parent 2568547384
commit 87724bb7b8
9 changed files with 297 additions and 352 deletions

View file

@ -151,7 +151,7 @@ const MenuItem = ({
} }
/> />
)} )}
{badge && badge > 0 ? ( {open && badge && badge > 0 ? (
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Typography <Typography
color="textSecondary" color="textSecondary"
@ -197,7 +197,6 @@ export const Sidebar: FC<SidebarProps> = ({
useEffect(() => { useEffect(() => {
const fetchCounts = async () => { const fetchCounts = async () => {
const counts = await getOverviewTicketCountsAction(); const counts = await getOverviewTicketCountsAction();
console.log({ counts });
setOverviewCounts(counts); setOverviewCounts(counts);
}; };
@ -422,8 +421,9 @@ export const Sidebar: FC<SidebarProps> = ({
/> />
<Collapse <Collapse
in={ in={
pathname.startsWith("/overview") || open &&
pathname.startsWith("/tickets") (pathname.startsWith("/overview") ||
pathname.startsWith("/tickets"))
} }
timeout="auto" timeout="auto"
unmountOnExit unmountOnExit
@ -512,7 +512,7 @@ export const Sidebar: FC<SidebarProps> = ({
/> />
)} )}
<Collapse <Collapse
in={pathname.startsWith("/leafcutter")} in={open && pathname.startsWith("/leafcutter")}
timeout="auto" timeout="auto"
unmountOnExit unmountOnExit
onClick={undefined} onClick={undefined}
@ -575,7 +575,7 @@ export const Sidebar: FC<SidebarProps> = ({
open={open} open={open}
/> />
<Collapse <Collapse
in={pathname.startsWith("/admin/")} in={open && pathname.startsWith("/admin/")}
timeout="auto" timeout="auto"
unmountOnExit unmountOnExit
onClick={undefined} onClick={undefined}
@ -588,7 +588,7 @@ export const Sidebar: FC<SidebarProps> = ({
open={open} open={open}
/> />
<Collapse <Collapse
in={pathname.startsWith("/admin/bridge")} in={open && pathname.startsWith("/admin/bridge")}
timeout="auto" timeout="auto"
unmountOnExit unmountOnExit
onClick={undefined} onClick={undefined}

View file

@ -20,7 +20,7 @@ export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
fetchTickets(); fetchTickets();
const interval = setInterval(fetchTickets, 20000); const interval = setInterval(fetchTickets, 10000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [name]); }, [name]);

View file

@ -61,113 +61,115 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
const shouldRender = !!ticket && !!ticketArticles; const shouldRender = !!ticket && !!ticketArticles;
return ( return (
shouldRender && ( <Box sx={{ height: "100%", width: "100%", background: veryLightGray }}>
<Box sx={{ height: "100%", width: "100%" }}> {shouldRender && (
<MainContainer> <>
<ChatContainer> <MainContainer>
<ConversationHeader> <ChatContainer>
<ConversationHeader.Content> <ConversationHeader>
<Box <ConversationHeader.Content>
sx={{ <Box
width: "100%",
textAlign: "center",
fontWeight: "bold",
}}
>
<Typography
variant="h5"
sx={{ sx={{
fontFamily: poppins.style.fontFamily, width: "100%",
fontWeight: 700, textAlign: "center",
fontWeight: "bold",
}} }}
> >
{ticket.title} <Typography
</Typography> variant="h5"
<Typography sx={{
variant="h6" fontFamily: poppins.style.fontFamily,
sx={{ fontWeight: 700,
fontFamily: roboto.style.fontFamily, }}
fontWeight: 400, >
{ticket.title}
</Typography>
<Typography
variant="h6"
sx={{
fontFamily: roboto.style.fontFamily,
fontWeight: 400,
}}
>{`Ticket #${ticket.number} (created ${new Date(
ticket.createdAt,
).toLocaleDateString()})`}</Typography>
</Box>
</ConversationHeader.Content>
</ConversationHeader>
<MessageList style={{ marginBottom: 80 }}>
{ticketArticles.edges.map(({ node: article }: any) => (
<Message
key={article.id}
className={
article.internal
? "internal-note"
: article?.sender?.name === "Agent"
? "outgoing-message"
: "incoming-message"
}
model={{
message: article.bodyWithUrls,
sentTime: article.updated_at,
sender: article.from,
direction:
article.sender === "Agent" ? "outgoing" : "incoming",
position: "single",
}} }}
>{`Ticket #${ticket.number} (created ${new Date( />
ticket.createdAt, ))}
).toLocaleDateString()})`}</Typography> </MessageList>
</Box> </ChatContainer>
</ConversationHeader.Content> <Box
</ConversationHeader> sx={{
<MessageList style={{ marginBottom: 80 }}> height: 80,
{ticketArticles.edges.map(({ node: article }: any) => ( background: veryLightGray,
<Message borderTop: `1px solid ${lightGray}`,
key={article.id} position: "absolute",
className={ bottom: 0,
article.internal width: "100%",
? "internal-note" zIndex: 1000,
: article?.sender?.name === "Agent" }}
? "outgoing-message"
: "incoming-message"
}
model={{
message: article.bodyWithUrls,
sentTime: article.updated_at,
sender: article.from,
direction:
article.sender === "Agent" ? "outgoing" : "incoming",
position: "single",
}}
/>
))}
</MessageList>
</ChatContainer>
<Box
sx={{
height: 80,
background: veryLightGray,
borderTop: `1px solid ${lightGray}`,
position: "absolute",
bottom: 0,
width: "100%",
zIndex: 1000,
}}
>
<Grid
container
spacing={6}
justifyContent="center"
alignItems="center"
alignContent="center"
sx={{ height: "100%", pt: 6 }}
> >
<Grid item> <Grid
<Button container
text="Write note to agent" spacing={6}
color="#FFB620" justifyContent="center"
onClick={() => { alignItems="center"
setArticleKind("note"); alignContent="center"
setDialogOpen(true); sx={{ height: "100%", pt: 6 }}
}} >
/> <Grid item>
<Button
text="Write note to agent"
color="#FFB620"
onClick={() => {
setArticleKind("note");
setDialogOpen(true);
}}
/>
</Grid>
<Grid item>
<Button
text="Reply to ticket"
kind="primary"
onClick={() => {
setArticleKind(firstArticleKind);
setDialogOpen(true);
}}
/>
</Grid>
</Grid> </Grid>
<Grid item> </Box>
<Button </MainContainer>
text="Reply to ticket" <ArticleCreateDialog
kind="primary" ticketID={ticket.internalId}
onClick={() => { open={dialogOpen}
setArticleKind(firstArticleKind); closeDialog={closeDialog}
setDialogOpen(true); kind={articleKind}
}} recipient={recipient}
/> />
</Grid> </>
</Grid> )}
</Box> </Box>
</MainContainer>
<ArticleCreateDialog
ticketID={ticket.internalId}
open={dialogOpen}
closeDialog={closeDialog}
kind={articleKind}
recipient={recipient}
/>
</Box>
)
); );
}; };

View file

@ -1,18 +1,15 @@
"use client"; "use client";
import { FC, useEffect, useState } from "react"; import { FC, useEffect, useState } from "react";
import { Grid, Box, MenuItem } from "@mui/material"; import { Grid, Box } from "@mui/material";
import { useFormState } from "react-dom";
import { Select, Button } from "@link-stack/ui"; import { Select, Button } from "@link-stack/ui";
import { MuiChipsInput } from "mui-chips-input"; import { MuiChipsInput } from "mui-chips-input";
import { getTicketQuery } from "app/_graphql/getTicketQuery";
import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { import {
updateTicketAction, updateTicketAction,
getTicketAction, getTicketAction,
getTicketStatesAction, getTicketStatesAction,
getTicketPrioritiesAction, getTicketPrioritiesAction,
getTicketTagsAction,
} from "app/_actions/tickets"; } from "app/_actions/tickets";
import { getAgentsAction } from "app/_actions/users"; import { getAgentsAction } from "app/_actions/users";
import { getGroupsAction } from "app/_actions/groups"; import { getGroupsAction } from "app/_actions/groups";
@ -23,56 +20,51 @@ interface TicketEditProps {
export const TicketEdit: FC<TicketEditProps> = ({ id }) => { export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
const [ticket, setTicket] = useState<any>(); const [ticket, setTicket] = useState<any>();
const [hasChanges, setHasChanges] = useState(false);
const [formState, setFormState] = useState({
values: {
group: null,
owner: null,
priority: null,
pendingTime: null,
state: null,
tags: [],
},
});
const [ticketStates, setTicketStates] = useState<any>(); const [ticketStates, setTicketStates] = useState<any>();
const [ticketPriorities, setTicketPriorities] = useState<any>(); const [ticketPriorities, setTicketPriorities] = useState<any>();
const [groups, setGroups] = useState<any>(); const [groups, setGroups] = useState<any>();
const [tags, setTags] = useState<any>();
const [agents, setAgents] = useState<any>(); const [agents, setAgents] = useState<any>();
const selectedTags = []; const [pendingVisible, setPendingVisible] = useState(false);
const pendingVisible = false;
const pendingDate = new Date();
const handleDelete = () => {
console.info("You clicked the delete icon.");
};
const filteredStates = ticketStates?.filter( const filteredStates =
(state: any) => !["new", "merged", "removed"].includes(state.name), ticketStates?.filter(
); (state: any) => !["new", "merged", "removed"].includes(state.label),
) ?? [];
useEffect(() => { useEffect(() => {
const fetchAgents = async () => { const fetchAgents = async () => {
const result = await getAgentsAction(); const result = await getAgentsAction();
console.log({ agents: result });
setAgents(result); setAgents(result);
}; };
const fetchGroups = async () => { const fetchGroups = async () => {
const result = await getGroupsAction(); const result = await getGroupsAction();
console.log({ groups: result });
setGroups(result); setGroups(result);
}; };
const fetchTicketStates = async () => { const fetchTicketStates = async () => {
const result = await getTicketStatesAction(); const result = await getTicketStatesAction();
console.log({ ticketStates: result });
setTicketStates(result); setTicketStates(result);
}; };
const fetchTicketPriorities = async () => { const fetchTicketPriorities = async () => {
const result = await getTicketPrioritiesAction(); const result = await getTicketPrioritiesAction();
console.log({ ticketPriorities: result });
setTicketPriorities(result); setTicketPriorities(result);
}; };
const fetchTicketTags = async () => {
const result = await getTicketTagsAction();
console.log({ tags: result });
setTags(result);
};
fetchTicketStates(); fetchTicketStates();
fetchTicketPriorities(); fetchTicketPriorities();
fetchTicketTags();
fetchAgents(); fetchAgents();
fetchGroups(); fetchGroups();
}, []); }, []);
@ -80,185 +72,134 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
useEffect(() => { useEffect(() => {
const fetchTicket = async () => { const fetchTicket = async () => {
const result = await getTicketAction(id); const result = await getTicketAction(id);
console.log({ result });
setTicket(result); setTicket(result);
setFormState({
values: {
...formState.values,
group: result?.group?.id,
owner: result?.owner?.id,
priority: result?.priority?.id,
state: result?.state?.id,
tags: result?.tags,
},
});
}; };
fetchTicket(); fetchTicket();
}, []); }, []);
/* const updateFormState = (name: string, value: any) => {
useEffect(() => { setFormState({
values: {
...formState.values,
[name]: value,
},
});
const stateName = filteredStates?.find( const stateName = filteredStates?.find(
(state: any) => state.id === selectedState, (state: any) => state.id === formState.values.state,
)?.name; )?.name;
setPendingVisible(stateName?.includes("pending") ?? false); setPendingVisible(stateName?.includes("pending") ?? false);
}, [selectedState]); setHasChanges(true);
*/
const updateTicket = async (input: any) => {
/*
console.log({ input });
const res = await fetcher({
document: updateTicketMutation,
variables: {
ticketId: `gid://zammad/Ticket/${id}`,
input,
},
});
console.log({ res });
*/
}; };
const updateTags = async (tags: string[]) => {
/* const updateTicket = async () => {
console.log({ tags }); await updateTicketAction(id, formState.values);
const res = await fetcher({ setHasChanges(false);
document: updateTagsMutation,
variables: {
objectId: `gid://zammad/Ticket/${id}`,
tags,
},
});
console.log({ res });
*/
}; };
const initialState = {
messages: [],
errors: [],
values: {
customer: "",
group: ticket?.group?.id?.split("/").pop(),
owner: ticket?.owner?.id?.split("/").pop(),
priority: ticket?.priority?.id?.split("/").pop(),
state: ticket?.state?.id?.split("/").pop(),
tags: [],
title: "",
article: {
body: "",
type: "note",
internal: true,
},
},
};
const [formState, formAction] = useFormState(
updateTicketAction,
initialState,
);
const shouldRender = !!ticket; const shouldRender = !!ticket;
return ( return (
shouldRender && ( <Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}> {shouldRender && (
<form action={formAction}> <Grid container direction="column" spacing={3}>
<Grid container direction="column" spacing={3}> <Grid item>
<Box sx={{ m: 1 }}>Group</Box>
<Select
name="group"
label="Group"
formState={formState}
updateFormState={updateFormState}
getOptions={() => groups}
/>
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
<Select
name="owner"
label="Owner"
formState={formState}
updateFormState={updateFormState}
getOptions={() => agents}
/>
</Grid>
<Grid item xs={12}>
<Box sx={{ m: 1, mt: 0 }}>State</Box>
<Select
name="state"
label="State"
formState={formState}
updateFormState={updateFormState}
getOptions={() => filteredStates}
/>
</Grid>
<Grid
item
xs={12}
sx={{ display: pendingVisible ? "inherit" : "none" }}
>
<DatePicker
label="Pending Date"
value={new Date(formState.values.pendingTime)}
onChange={(newValue: any) => {
updateFormState("pendingDate", newValue.toISOString());
}}
slotProps={{ textField: { size: "small" } }}
sx={{
width: "100%",
backgroundColor: "white",
}}
/>
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Priority</Box>
<Select
name="priority"
label="Priority"
formState={formState}
updateFormState={updateFormState}
getOptions={() => ticketPriorities}
/>
</Grid>
<Grid item>
<Box sx={{ mb: 1 }}>Tags</Box>
<MuiChipsInput
sx={{ backgroundColor: "white", width: "100%" }}
value={formState.values.tags}
hideClearAll
onChange={(tags: any) => {
updateFormState("tags", tags);
}}
onDeleteChip={(tag: any) => {
const tags = formState.values.tags.filter(
(t: any) => t !== tag,
);
updateFormState("tags", tags);
}}
/>
</Grid>
<Grid item container direction="row-reverse">
<Grid item> <Grid item>
<Box sx={{ m: 1 }}>Group</Box> <Button
<Select text="Save"
name="group" kind="primary"
label="Group" onClick={updateTicket}
formState={formState} disabled={!hasChanges}
getOptions={() => groups}
/> />
</Grid> </Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
<Select
name="owner"
label="Owner"
formState={formState}
getOptions={() => agents}
/>
</Grid>
<Grid item xs={12}>
<Box sx={{ m: 1, mt: 0 }}>State</Box>
<Select
name="state"
label="State"
formState={formState}
getOptions={() =>
filteredStates?.map((state: any) => ({
value: state.id,
label: state.name,
})) ?? []
}
/*
onChange={(e: any) => {
const newState = e.target.value;
setSelectedState(newState);
updateTicket({
stateId: `gid://zammad/Ticket::State/${newState}`,
pendingTime: pendingDate.toISOString(),
});
}}
*/
/>
</Grid>
<Grid
item
xs={12}
sx={{ display: pendingVisible ? "inherit" : "none" }}
>
{/* <DatePicker
label="Pending Date"
value={pendingDate}
onChange={(newValue: any) => {
console.log(newValue);
setPendingDate(newValue);
updateTicket({
pendingTime: newValue.toISOString(),
})
}}
slotProps={{ textField: { size: "small" } }}
sx={{
width: "100%",
backgroundColor: "white",
}}
/> */}
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Priority</Box>
<Select
name="priority"
label="Priority"
formState={formState}
getOptions={() => ticketPriorities}
/*
onChange={(e: any) => {
const newPriority = e.target.value;
setSelectedPriority(newPriority);
updateTicket({
priorityId: `gid://zammad/Ticket::Priority/${newPriority}`,
});
}}
*/
/>
</Grid>
<Grid item>
<Box sx={{ mb: 1 }}>Tags</Box>
<MuiChipsInput
sx={{ backgroundColor: "white", width: "100%" }}
value={selectedTags}
onChange={(tags: any) => {
/*
setSelectedTags(tags);
updateTags(tags);
*/
}}
/>
</Grid>
<Grid item container direction="row-reverse">
<Grid item>
<Button text="Save" kind="primary" type="submit" />
</Grid>
</Grid>
</Grid> </Grid>
</form> </Grid>
</Box> )}
) </Box>
); );
}; };

View file

@ -76,35 +76,59 @@ export const createTicketArticleAction = async (
}; };
export const updateTicketAction = async ( export const updateTicketAction = async (
currentState: any, ticketID: string,
formData: FormData, ticketInfo: Record<string, any>,
) => { ) => {
/* console.log({ ticketID, ticketInfo });
try { try {
const { id, project } = currentState.values; const input = {};
const updatedTicket = { if (ticketInfo.state) {
title: formData.get("title"), input["stateId"] = ticketInfo.state;
}; }
await executeMutation({ if (ticketInfo.pendingTime) {
project, input["pendingTime"] = ticketInfo.pendingTime;
mutation: UpdateAssetMutation, }
if (ticketInfo.priority) {
input["priorityId"] = ticketInfo.priority;
}
if (ticketInfo.group) {
input["groupId"] = ticketInfo.group;
}
if (ticketInfo.owner) {
input["ownerId"] = ticketInfo.owner;
}
const result = await executeGraphQL({
query: updateTicketMutation,
variables: { variables: {
id, ticketId: `gid://zammad/Ticket/${ticketID}`,
input: updatedAsset, input,
}, },
}); });
revalidatePath(`/${project}/assets/${id}`); if (ticketInfo.tags?.length > 0) {
const tagsResult = await executeGraphQL({
query: updateTagsMutation,
variables: {
objectId: `gid://zammad/Ticket/${ticketID}`,
tags: ticketInfo.tags,
},
});
console.log({ tagsResult });
}
console.log({ result });
return { return {
...currentState, result,
values: { ...currentState.values, ...updatedAsset, id, project },
success: true, success: true,
}; };
} catch (e: any) { } catch (e: any) {
return { success: false, message: e?.message ?? "Unknown error" }; console.log({ e });
return {
success: false,
message: e?.message ?? "Unknown error",
};
} }
*/
}; };
export const getTicketAction = async (id: string) => { export const getTicketAction = async (id: string) => {
@ -112,7 +136,7 @@ export const getTicketAction = async (id: string) => {
query: getTicketQuery, query: getTicketQuery,
variables: { ticketId: `gid://zammad/Ticket/${id}` }, variables: { ticketId: `gid://zammad/Ticket/${id}` },
}); });
console.log({ td: ticketData.ticket });
return ticketData?.ticket; return ticketData?.ticket;
}; };
@ -125,38 +149,6 @@ export const getTicketArticlesAction = async (id: string) => {
return ticketData?.ticketArticles; return ticketData?.ticketArticles;
}; };
export const updateTicketTagsAction = async (
currentState: any,
formData: FormData,
) => {
/*
try {
const { id, project } = currentState.values;
const updatedTicket = {
title: formData.get("title"),
};
await executeMutation({
project,
mutation: UpdateAssetMutation,
variables: {
id,
input: updatedAsset,
},
});
revalidatePath(`/${project}/assets/${id}`);
return {
...currentState,
values: { ...currentState.values, ...updatedAsset, id, project },
success: true,
};
} catch (e: any) {
return { success: false, message: e?.message ?? "Unknown error" };
}
*/
};
export const getTicketStatesAction = async () => { export const getTicketStatesAction = async () => {
const states = await executeREST({ const states = await executeREST({
path: "/api/v1/ticket_states", path: "/api/v1/ticket_states",
@ -164,15 +156,15 @@ export const getTicketStatesAction = async () => {
const formattedStates = const formattedStates =
states?.map((state: any) => ({ states?.map((state: any) => ({
value: state.id, value: `gid://zammad/Ticket::State/${state.id}`,
label: state.name, label: state.name,
})) ?? []; })) ?? [];
return formattedStates; return formattedStates;
}; };
export const getTicketTagsAction = async () => { export const getTagsAction = async () => {
const tags = await executeREST({ const { tags } = await executeREST({
path: "/api/v1/tags", path: "/api/v1/tags",
}); });
@ -186,7 +178,7 @@ export const getTicketPrioritiesAction = async () => {
const formattedPriorities = const formattedPriorities =
priorities?.map((priority: any) => ({ priorities?.map((priority: any) => ({
value: priority.id, value: `gid://zammad/Ticket::Priority/${priority.id}`,
label: priority.name, label: priority.name,
})) ?? []; })) ?? [];

View file

@ -12,7 +12,6 @@ export const CSRFProvider: FC<PropsWithChildren> = ({ children }) => {
const response = await fetch("/api/v1/users/me"); const response = await fetch("/api/v1/users/me");
const token = response.headers.get("CSRF-Token"); const token = response.headers.get("CSRF-Token");
update({ csrfToken: token }); update({ csrfToken: token });
console.log({ token });
} }
}, 30000); }, 30000);

View file

@ -6,6 +6,7 @@ const nextConfig = {
"@link-stack/ui", "@link-stack/ui",
"@link-stack/bridge-common", "@link-stack/bridge-common",
"@link-stack/bridge-ui", "@link-stack/bridge-ui",
"mui-chips-input"
], ],
publicRuntimeConfig: { publicRuntimeConfig: {
linkURL: process.env.LINK_URL ?? "http://localhost:3000", linkURL: process.env.LINK_URL ?? "http://localhost:3000",

View file

@ -5,6 +5,7 @@ import { colors } from "../styles/theme";
interface InternalButtonProps { interface InternalButtonProps {
text: string; text: string;
disabled: boolean;
color?: string; color?: string;
kind?: "primary" | "secondary" | "destructive"; kind?: "primary" | "secondary" | "destructive";
type?: string; type?: string;
@ -27,12 +28,14 @@ export const InternalButton: FC<InternalButtonProps> = ({
text, text,
color, color,
kind, kind,
disabled,
type = "button", type = "button",
onClick, onClick,
}) => ( }) => (
<MUIButton <MUIButton
variant="contained" variant="contained"
disableElevation disableElevation
disabled={disabled}
onClick={onClick} onClick={onClick}
type={type} type={type}
href="" href=""
@ -56,6 +59,7 @@ export const InternalButton: FC<InternalButtonProps> = ({
interface ButtonProps { interface ButtonProps {
text: string; text: string;
disabled?: boolean;
color?: string; color?: string;
kind?: "primary" | "secondary" | "destructive"; kind?: "primary" | "secondary" | "destructive";
type?: string; type?: string;
@ -65,6 +69,7 @@ interface ButtonProps {
export const Button: FC<ButtonProps> = ({ export const Button: FC<ButtonProps> = ({
text, text,
disabled = false,
color, color,
type, type,
kind, kind,
@ -75,6 +80,7 @@ export const Button: FC<ButtonProps> = ({
<Link href={href} passHref> <Link href={href} passHref>
<InternalButton <InternalButton
text={text} text={text}
disabled={disabled}
color={color} color={color}
kind={kind} kind={kind}
type={type} type={type}
@ -84,6 +90,7 @@ export const Button: FC<ButtonProps> = ({
) : ( ) : (
<InternalButton <InternalButton
text={text} text={text}
disabled={disabled}
color={color} color={color}
kind={kind} kind={kind}
type={type} type={type}

View file

@ -16,6 +16,7 @@ type TextFieldProps = {
required?: boolean; required?: boolean;
lines?: number; lines?: number;
helperText?: string; helperText?: string;
updateFormState?: (field: string, value: any) => void;
}; };
export const TextField: FC<TextFieldProps> = ({ export const TextField: FC<TextFieldProps> = ({
@ -27,6 +28,7 @@ export const TextField: FC<TextFieldProps> = ({
required = false, required = false,
lines = 1, lines = 1,
helperText, helperText,
updateFormState,
}) => { }) => {
const { darkMediumGray, white } = colors; const { darkMediumGray, white } = colors;
const { roboto } = fonts; const { roboto } = fonts;
@ -42,6 +44,7 @@ export const TextField: FC<TextFieldProps> = ({
rows={lines} rows={lines}
required={required} required={required}
defaultValue={formState.values[name]} defaultValue={formState.values[name]}
onChange={(e: any) => updateFormState?.(name, e.target.value)}
error={Boolean(formState.errors[name])} error={Boolean(formState.errors[name])}
helperText={formState.errors[name] ?? helperText} helperText={formState.errors[name] ?? helperText}
InputProps={{ InputProps={{