mirror of
https://github.com/rjNemo/ticket_manager
synced 2026-06-12 11:46:40 +00:00
Created NewProjectModal not implemented yet
This commit is contained in:
parent
8a3e1d96d5
commit
1368859860
8 changed files with 321 additions and 50 deletions
|
|
@ -56,5 +56,5 @@
|
||||||
- [ ] Refactor Lists code
|
- [ ] Refactor Lists code
|
||||||
- [ ] Query project members in UserPage
|
- [ ] Query project members in UserPage
|
||||||
- [ ] Query progression info in UserPage
|
- [ ] Query progression info in UserPage
|
||||||
- [ ] Add info fields in New Ticket Form
|
- [x] Add info fields in New Ticket Form
|
||||||
- [ ] Filter users in Users Modal
|
- [ ] Filter users in Users Modal
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,9 @@ export class UserVM {
|
||||||
public projects: Project[];
|
public projects: Project[];
|
||||||
public tickets: Ticket[];
|
public tickets: Ticket[];
|
||||||
public activities: Activity[];
|
public activities: Activity[];
|
||||||
|
public allUsers: User[];
|
||||||
|
|
||||||
public constructor(user: User) {
|
public constructor(user: User, allUsers: User[]) {
|
||||||
this.id = user.id;
|
this.id = user.id;
|
||||||
this.firstName = user.firstName;
|
this.firstName = user.firstName;
|
||||||
this.lastName = user.lastName;
|
this.lastName = user.lastName;
|
||||||
|
|
@ -30,5 +31,6 @@ export class UserVM {
|
||||||
this.projects = user.projects;
|
this.projects = user.projects;
|
||||||
this.tickets = user.tickets;
|
this.tickets = user.tickets;
|
||||||
this.activities = user.activities;
|
this.activities = user.activities;
|
||||||
|
this.allUsers = allUsers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,38 @@ import {
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { FilterBar } from "../FilterBar";
|
import { FilterBar } from "../FilterBar";
|
||||||
import ProjectCard from "../Cards/ProjectCard";
|
import ProjectCard from "../Cards/ProjectCard";
|
||||||
|
import { FloatingButton } from "../Buttons/FloatingButton";
|
||||||
|
import { NewProjectModal } from "../Modals/NewProjectModal";
|
||||||
import { Project } from "../../types/Project";
|
import { Project } from "../../types/Project";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
header: {
|
header: {
|
||||||
paddingBottom: theme.spacing(2),
|
paddingBottom: theme.spacing(2),
|
||||||
},
|
},
|
||||||
|
addButton: {
|
||||||
|
position: "relative",
|
||||||
|
marginLeft: "20px",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
projects: Project[];
|
projects: Project[];
|
||||||
|
allUsers: User[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectList: FC<IProps> = ({ projects }) => {
|
export const ProjectList: FC<IProps> = ({ projects, allUsers }) => {
|
||||||
const [filterText, setFilterText] = useState<string>("");
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||||
setFilterText("");
|
setFilterText("");
|
||||||
};
|
};
|
||||||
|
const [showNew, setShowNew] = useState(false);
|
||||||
|
const onClick = (e: MouseEvent): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowNew(true);
|
||||||
|
};
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
setFilterText(e.target.value);
|
setFilterText(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
@ -41,47 +53,59 @@ export const ProjectList: FC<IProps> = ({ projects }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container>
|
<>
|
||||||
<Grid
|
<NewProjectModal
|
||||||
container
|
handleClose={() => {
|
||||||
direction="row"
|
setShowNew(false);
|
||||||
justify="space-between"
|
}}
|
||||||
alignItems="center"
|
show={showNew}
|
||||||
className={classes.header}
|
allUsers={allUsers}
|
||||||
>
|
/>
|
||||||
<Typography variant="h4" component="h4">
|
<Grid container>
|
||||||
Projects
|
<Grid
|
||||||
</Typography>
|
container
|
||||||
<FilterBar
|
direction="row"
|
||||||
filterText={filterText}
|
justify="space-between"
|
||||||
handleChange={handleChange}
|
alignItems="center"
|
||||||
clearFilterText={clearFilterText}
|
className={classes.header}
|
||||||
/>
|
>
|
||||||
|
<Typography variant="h4" component="h4">
|
||||||
|
Projects
|
||||||
|
<span className={classes.addButton}>
|
||||||
|
<FloatingButton color="primary" size="small" onClick={onClick} />
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
<FilterBar
|
||||||
|
filterText={filterText}
|
||||||
|
handleChange={handleChange}
|
||||||
|
clearFilterText={clearFilterText}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div className="col s12 grey lighten-1">
|
||||||
|
{filteredProjects.length === 0 ? (
|
||||||
|
<ProjectCard />
|
||||||
|
) : (
|
||||||
|
filteredProjects.map((t: Project) => (
|
||||||
|
<ProjectCard
|
||||||
|
key={t.id}
|
||||||
|
title={t.title}
|
||||||
|
remainingDays={t.endingDate}
|
||||||
|
link={`/projects/${t.id}`}
|
||||||
|
members={t.users}
|
||||||
|
progress={t.progression}
|
||||||
|
ticketsNumber={t.tickets === undefined ? 0 : t.tickets.length}
|
||||||
|
ticketsDone={
|
||||||
|
t.tickets === undefined
|
||||||
|
? 0
|
||||||
|
: t.tickets.filter((t) => t.status === "Done").length
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
</>
|
||||||
<div className="col s12 grey lighten-1">
|
|
||||||
{filteredProjects.length === 0 ? (
|
|
||||||
<ProjectCard />
|
|
||||||
) : (
|
|
||||||
filteredProjects.map((t: Project) => (
|
|
||||||
<ProjectCard
|
|
||||||
key={t.id}
|
|
||||||
title={t.title}
|
|
||||||
remainingDays={t.endingDate}
|
|
||||||
link={`/projects/${t.id}`}
|
|
||||||
members={t.users}
|
|
||||||
progress={t.progression}
|
|
||||||
ticketsNumber={t.tickets === undefined ? 0 : t.tickets.length}
|
|
||||||
ticketsDone={
|
|
||||||
t.tickets === undefined
|
|
||||||
? 0
|
|
||||||
: t.tickets.filter((t) => t.status === "Done").length
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
214
client/src/components/Modals/NewProjectModal.tsx
Normal file
214
client/src/components/Modals/NewProjectModal.tsx
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
import React, { FC, useState, FormEvent } from "react";
|
||||||
|
import { useRouteMatch } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
Grid,
|
||||||
|
makeStyles,
|
||||||
|
Theme,
|
||||||
|
createStyles,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
import { Ticket } from "../../types/Ticket";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
import { post } from "../../utils/http";
|
||||||
|
import { Constants } from "../../utils/Constants";
|
||||||
|
import Category from "../../types/enums/category";
|
||||||
|
import Impact from "../../types/enums/impact";
|
||||||
|
import Difficulty from "../../types/enums/difficulty";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
show: boolean;
|
||||||
|
handleClose: () => void;
|
||||||
|
allUsers: User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
select: {
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const NewProjectModal: FC<IProps> = ({
|
||||||
|
show,
|
||||||
|
handleClose,
|
||||||
|
allUsers,
|
||||||
|
}) => {
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
|
const [endingDate, setEndingDate] = useState("");
|
||||||
|
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
const id = url.split("/")[2];
|
||||||
|
const [projectId, setProjectId] = useState(id);
|
||||||
|
const [categoryID, setCategoryID] = useState(0);
|
||||||
|
const [impactID, setImpactID] = useState(0);
|
||||||
|
const [difficultyID, setDifficultyID] = useState(0);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let newTicket = {
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
endingDate: new Date(endingDate).toISOString(),
|
||||||
|
creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94", // get current User id
|
||||||
|
projectId: parseInt(projectId),
|
||||||
|
impact: impactID,
|
||||||
|
difficulty: difficultyID,
|
||||||
|
category: categoryID,
|
||||||
|
};
|
||||||
|
|
||||||
|
// const response: HttpResponse<Ticket> =
|
||||||
|
await post<Ticket>(`${Constants.ticketsURI}`, newTicket);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
name="New Ticket"
|
||||||
|
show={show}
|
||||||
|
handleClose={handleClose}
|
||||||
|
action="New Ticket"
|
||||||
|
handleAction={handleSubmit}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="title"
|
||||||
|
value={title}
|
||||||
|
label="Title"
|
||||||
|
name="text"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setTitle(e.target.value)
|
||||||
|
}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="description"
|
||||||
|
value={description}
|
||||||
|
label="Description"
|
||||||
|
name="text"
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
|
setDescription(e.target.value)
|
||||||
|
}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
id="project"
|
||||||
|
name="project"
|
||||||
|
select
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
label="Project"
|
||||||
|
value={projectId}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setProjectId(e.target.value);
|
||||||
|
}}
|
||||||
|
// helperText="Please select your currency"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
{allUsers.map((p) => (
|
||||||
|
<MenuItem key={p.id} value={p.id}>
|
||||||
|
{p}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
id="date"
|
||||||
|
name="date"
|
||||||
|
label="Due Date"
|
||||||
|
type="date"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
// defaultValue={new Date().toISOString()}
|
||||||
|
// className={classes.textField}
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
required
|
||||||
|
value={endingDate}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setEndingDate(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Grid container justify="space-between">
|
||||||
|
<TextField
|
||||||
|
id="category"
|
||||||
|
name="category"
|
||||||
|
select
|
||||||
|
label="Category"
|
||||||
|
value={categoryID}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setCategoryID(parseInt(e.target.value));
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
className={classes.select}
|
||||||
|
>
|
||||||
|
{Category.map((c: string, i: number) => (
|
||||||
|
<MenuItem key={i} value={i}>
|
||||||
|
{c}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
className={classes.select}
|
||||||
|
id="impact"
|
||||||
|
name="impact"
|
||||||
|
select
|
||||||
|
label="Impact"
|
||||||
|
value={impactID}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setImpactID(parseInt(e.target.value));
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
{Impact.map((c: string, i: number) => (
|
||||||
|
<MenuItem key={i} value={i}>
|
||||||
|
{c}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
className={classes.select}
|
||||||
|
id="difficulty"
|
||||||
|
name="difficulty"
|
||||||
|
select
|
||||||
|
label="Difficulty"
|
||||||
|
value={difficultyID}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDifficultyID(parseInt(e.target.value));
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
{Difficulty.map((c: string, i: number) => (
|
||||||
|
<MenuItem key={i} value={i}>
|
||||||
|
{c}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -10,6 +10,7 @@ import { Ticket } from "../../types/Ticket";
|
||||||
import { Project } from "../../types/Project";
|
import { Project } from "../../types/Project";
|
||||||
import { ProjectList } from "../Lists/ProjectList";
|
import { ProjectList } from "../Lists/ProjectList";
|
||||||
import { TicketList } from "../Lists/TicketList";
|
import { TicketList } from "../Lists/TicketList";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
|
||||||
interface TabProps {
|
interface TabProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|
@ -53,9 +54,15 @@ interface IProps {
|
||||||
tabNames: string[];
|
tabNames: string[];
|
||||||
tickets: Ticket[];
|
tickets: Ticket[];
|
||||||
projects: Project[];
|
projects: Project[];
|
||||||
|
allUsers: User[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserTabPanel: FC<IProps> = ({ tickets, tabNames, projects }) => {
|
export const UserTabPanel: FC<IProps> = ({
|
||||||
|
tickets,
|
||||||
|
tabNames,
|
||||||
|
projects,
|
||||||
|
allUsers,
|
||||||
|
}) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [value, setValue] = useState(0);
|
const [value, setValue] = useState(0);
|
||||||
|
|
@ -94,7 +101,7 @@ export const UserTabPanel: FC<IProps> = ({ tickets, tabNames, projects }) => {
|
||||||
onChangeIndex={handleChangeIndex}
|
onChangeIndex={handleChangeIndex}
|
||||||
>
|
>
|
||||||
<TabPanel value={value} index={0} dir={theme.direction}>
|
<TabPanel value={value} index={0} dir={theme.direction}>
|
||||||
<ProjectList projects={projects} />
|
<ProjectList projects={projects} allUsers={allUsers} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={value} index={1} dir={theme.direction}>
|
<TabPanel value={value} index={1} dir={theme.direction}>
|
||||||
<TicketList tickets={tickets} allProjects={[]} addButton={false} />
|
<TicketList tickets={tickets} allProjects={[]} addButton={false} />
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export const UserTabRouter: FC<IProps> = ({ tickets, tabNames, projects }) => {
|
||||||
<Redirect from={url} to={`${url}/projects`} />
|
<Redirect from={url} to={`${url}/projects`} />
|
||||||
|
|
||||||
<Route path={`${url}/projects`}>
|
<Route path={`${url}/projects`}>
|
||||||
<ProjectList projects={projects} />
|
{/* <ProjectList projects={projects} /> */}
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={`${url}/tickets`}>
|
<Route path={`${url}/tickets`}>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export const UserController: FC = () => {
|
||||||
const [user, setUser] = useState<User>({} as User);
|
const [user, setUser] = useState<User>({} as User);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [allUsers, setAllUsers] = useState<User[]>([]);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
async function httpGetUser(id: string): Promise<void> {
|
async function httpGetUser(id: string): Promise<void> {
|
||||||
|
|
@ -32,9 +33,24 @@ export const UserController: FC = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function httpGetAllUsers(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response: HttpResponse<User> = await get<User>(
|
||||||
|
`${Constants.usersURI}`
|
||||||
|
);
|
||||||
|
if (response.parsedBody !== undefined) {
|
||||||
|
setAllUsers((response.parsedBody as unknown) as User[]);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
setHasError(true);
|
||||||
|
setError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id !== undefined) {
|
if (id !== undefined) {
|
||||||
httpGetUser(id);
|
httpGetUser(id);
|
||||||
|
httpGetAllUsers();
|
||||||
} else {
|
} else {
|
||||||
setHasError(true);
|
setHasError(true);
|
||||||
setError("Bad Request");
|
setError("Bad Request");
|
||||||
|
|
@ -45,6 +61,6 @@ export const UserController: FC = () => {
|
||||||
return <ErrorController error={error} />;
|
return <ErrorController error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewModel = new UserVM(user);
|
const viewModel = new UserVM(user, allUsers);
|
||||||
return isLoading ? <Preloader /> : <UserPage viewModel={viewModel} />;
|
return isLoading ? <Preloader /> : <UserPage viewModel={viewModel} />;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserPage: FC<IProps> = ({ viewModel }) => {
|
export const UserPage: FC<IProps> = ({ viewModel }) => {
|
||||||
const { fullName, presentation, picture, projects, tickets } = viewModel;
|
const {
|
||||||
|
fullName,
|
||||||
|
presentation,
|
||||||
|
picture,
|
||||||
|
projects,
|
||||||
|
tickets,
|
||||||
|
allUsers,
|
||||||
|
} = viewModel;
|
||||||
const tabNames: string[] = ["Projects", "Tickets"];
|
const tabNames: string[] = ["Projects", "Tickets"];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -26,6 +33,7 @@ export const UserPage: FC<IProps> = ({ viewModel }) => {
|
||||||
tabNames={tabNames}
|
tabNames={tabNames}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
tickets={tickets}
|
tickets={tickets}
|
||||||
|
allUsers={allUsers}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue