pulled all updates

This commit is contained in:
Ruidy Nemausat 2020-03-05 14:22:08 +01:00
commit 0e6ccd8d62
29 changed files with 719 additions and 121 deletions

View file

@ -92,7 +92,7 @@ namespace TicketManager.Controllers
{ {
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return BadRequest(); return BadRequest(ModelState);
} }
var ticket = new Ticket() var ticket = new Ticket()

View file

@ -8,7 +8,7 @@
### v1 ### v1
- [Internal Link. Don't forget to update](https://localhost:5001/swagger) - [Internal Link. Don't forget to update](https://localhost:5001/api/v1/)
## Features ## Features
@ -45,7 +45,8 @@
- [ ] logging - [ ] logging
- [ ] check useRef, useReducer, dispatch - [ ] check useRef, useReducer, dispatch
- [ ] error page redirect when offline. - [ ] error page redirect when offline.
- [ ] ticket/files/activities list placeholders when empty - [x] ticket/files/activities list placeholders when empty
- [ ] think about public/private DTO's constructor, getters and setters - [ ] think about public/private DTO's constructor, getters and setters
- [<span style="color:red">X</span>] write dtos without circular dependencies. - [<span style="color:red">X</span>] write dtos without circular dependencies.
- [ ] use dtoRequest for PutProjects - [ ] use dtoRequest for PutProjects
- [ ] render avatarlist after UserModal Update

View file

@ -90,7 +90,6 @@ namespace TicketManager
app.UseDefaultFiles(); app.UseDefaultFiles();
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {
c.RoutePrefix = "api/v1"; c.RoutePrefix = "api/v1";

View file

@ -6,7 +6,7 @@ import { User } from "../types/User";
import { getRemainingdays } from "../utils/methods"; import { getRemainingdays } from "../utils/methods";
export default class ProjectVM { export default class ProjectVM {
public id: number; // public id: number;
public title: string; public title: string;
public description: string; public description: string;
public creationDate: string; public creationDate: string;
@ -24,7 +24,7 @@ export default class ProjectVM {
public remainingDays: number; public remainingDays: number;
public constructor(project: Project, allUsers: User[]) { public constructor(project: Project, allUsers: User[]) {
this.id = project.id; // this.id = project.id;
this.title = project.title; this.title = project.title;
this.description = project.description; this.description = project.description;
this.creationDate = project.creationDate; this.creationDate = project.creationDate;

View file

@ -1,5 +1,19 @@
export class UserVM { import { Project } from "../types/Project";
public Id?: number; import { Ticket } from "../types/Ticket";
import { User } from "../types/User";
public constructor() {} export class UserVM {
public fullName: string;
public presentation: string;
public picture: string;
public projects: Project[];
public tickets: Ticket[];
public constructor(user: User) {
this.fullName = user.fullName;
this.presentation = user.presentation;
this.picture = user.picture;
this.projects = user.projects;
this.tickets = user.tickets;
}
} }

View file

@ -12,38 +12,58 @@ export const ActivityCollection: FC<IProps> = ({ activities, filterText }) => {
) : ( ) : (
<> <>
<ul className="collection"> <ul className="collection">
{activities {activities.length === 0 ? (
.filter( <ActivityEntry />
a => ) : (
a.description.toLowerCase().includes(filterText.toLowerCase()) || activities
a.user.firstName .filter(
.toLowerCase() a =>
.includes(filterText.toLowerCase()) || a.description
a.ticket.title.toLowerCase().includes(filterText.toLowerCase()) .toLowerCase()
) .includes(filterText.toLowerCase()) ||
.map((activity: Activity) => ( a.user.firstName
<li key={activity.id} className="collection-item avatar"> .toLowerCase()
<ActivityEntry activity={activity} /> .includes(filterText.toLowerCase()) ||
</li> a.ticket.title.toLowerCase().includes(filterText.toLowerCase())
))} )
.map((activity: Activity) => (
<ActivityEntry activity={activity} key={activity.id} />
))
)}
</ul> </ul>
</> </>
); );
}; };
type IFProps = { type IFProps = {
activity: Activity; activity?: Activity;
}; };
export const ActivityEntry: FC<IFProps> = ({ activity }) => { export const ActivityEntry: FC<IFProps> = ({ activity }) => {
return ( return (
<> <>
<img src={activity.user.picture} alt="" className="circle" /> <li className="collection-item avatar">
{/* <i className="material-icons circle">folder</i> */} {/* <img
<span className="title"> src={
{activity.user.firstName} {activity.description} {activity.ticket.title} activity
</span> ? activity.user.picture
<p>{activity.date.toDateString()}</p> : "https://previews.123rf.com/images/vikpit/vikpit1604/vikpit160400034/54976526-welcome-sign-symbol-word-welcome-hand-lettering-calligraphic-font-letters-and-shade-isolated-on-whit.jpg"
}
alt=""
height="32vh"
width="32vh"
className="circle"
/> */}
<i className="material-icons circle indigo lighten-1">folder</i>
<span className="title">
{activity ? activity.user.firstName : "Ruidy"}
{activity ? activity.description : " welcomes you "}
{activity ? activity.ticket.title : "here"}
</span>
<p>
{activity ? activity.date.toDateString() : new Date().toDateString()}
</p>
</li>
</> </>
); );
}; };

View file

@ -0,0 +1,12 @@
import React, { FC } from "react";
interface IProps {
picture: string;
}
export const Avatar: FC<IProps> = ({ picture }) => {
return (
<>
<img className="circle" src={picture} height="100vh" width="100vh" />
</>
);
};

View file

@ -7,35 +7,38 @@ type IProps = {
}; };
export const FileCollection: FC<IProps> = ({ files, filterText }) => { export const FileCollection: FC<IProps> = ({ files, filterText }) => {
console.log();
return ( return (
<> <>
<ul className="collection"> <ul className="collection">
{files {files.length === 0 ? (
.filter( <FileEntry />
f => ) : (
f.name.toLowerCase().includes(filterText.toLowerCase()) || files
f.format.toLowerCase().includes(filterText.toLowerCase()) .filter(
) f =>
.map((file: AppFile) => ( f.name.toLowerCase().includes(filterText.toLowerCase()) ||
<FileEntry file={file} key={file.id} /> f.format.toLowerCase().includes(filterText.toLowerCase())
))} )
.map((file: AppFile) => <FileEntry file={file} key={file.id} />)
)}
</ul> </ul>
</> </>
); );
}; };
type IFProps = { type IFProps = {
file: AppFile; file?: AppFile;
}; };
export const FileEntry: FC<IFProps> = ({ file }) => { export const FileEntry: FC<IFProps> = ({ file }) => {
return ( return (
<li className="collection-item avatar"> <li className="collection-item avatar">
{/* <img src={require("../images/user_1.jpg")} alt="" className="circle" /> */} {/* <img src={require("../images/user_1.jpg")} alt="" className="circle" /> */}
<i className="material-icons circle">folder</i> <i className="material-icons circle indigo lighten-1">folder</i>
<span className="title">{file.name}</span> <span className="title">{file ? file.name : "Add your first file"}</span>
<p> <p>
{file.size}kb {file.format} {file ? file.size : 0}kb {file ? file.format : "pdf"}
</p> </p>
<a href="#!" className="secondary-content"> <a href="#!" className="secondary-content">
<i className="material-icons">more_vert</i> <i className="material-icons">more_vert</i>

View file

@ -3,46 +3,58 @@ import { Link } from "react-router-dom";
import { getRemainingdays } from "../utils/methods"; import { getRemainingdays } from "../utils/methods";
interface IProps { interface IProps {
title: string; title?: string;
remainingDays: string; remainingDays?: string;
validateTicket: (event: MouseEvent) => void; validateTicket?: (event: MouseEvent) => void;
archiveTicket: (event: MouseEvent) => void; // archiveTicket: (event: MouseEvent) => void;
} }
export const HorizontalCard: FC<IProps> = ({ export const HorizontalCard: FC<IProps> = ({
title, title,
remainingDays, remainingDays,
archiveTicket, // archiveTicket,
validateTicket validateTicket
}) => { }) => {
return ( return (
<div className="card horizontal"> <li>
<div className="card-stacked"> <div className="card horizontal">
<div className="card-content"> <div className="card-stacked">
<div className="row"> <div className="card-content">
<div className="card-title"> <div className="row">
<h6> <div className="card-title">
<h6>
<Link to="#">
<b>{title ?? "Nothing to do"}</b>
</Link>
</h6>
</div>
<span>
Due{" "}
{remainingDays ? (
getRemainingdays(remainingDays)
) : (
<span>
<del>Too much</del> 0
</span>
)}{" "}
days
</span>
<div className="right">
<Link to="#"> <Link to="#">
<b>{title}</b> <i className="material-icons" onClick={validateTicket}>
check
</i>
</Link> </Link>
</h6> {/* <Link to="#">
</div>
<span>Due {getRemainingdays(remainingDays)} days</span>
<div className="right">
<Link to="#">
<i className="material-icons" onClick={validateTicket}>
check
</i>
</Link>
<Link to="#">
<i className="material-icons" onClick={archiveTicket}> <i className="material-icons" onClick={archiveTicket}>
archive archive
</i> </i>
</Link> </Link> */}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </li>
); );
}; };

View file

@ -7,7 +7,7 @@ export const InputFile: FC<IProps> = () => {
<> <>
<form action="/upload"> <form action="/upload">
<div className="file-field input-field"> <div className="file-field input-field">
<div className="btn"> <div className="btn indigo lighten-1">
<i className="material-icons ">cloud_upload</i> <i className="material-icons ">cloud_upload</i>
<input <input
type="file" type="file"

View file

@ -0,0 +1,41 @@
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
import { UsersModalEntry } from "./UsersModalEntry";
import { FilterBar } from "./FilterBar";
import { User } from "../types/User";
interface IProps {
users: User[];
}
export const MemberList: FC<IProps> = ({ users }) => {
const [members, setMembers] = useState<User[]>([]);
const [filterText, setFilterText] = useState<string>("");
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
setFilterText("");
};
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
e: ChangeEvent<HTMLInputElement>
) => {
setFilterText(e.target.value);
};
return (
<>
<FilterBar
filterText={filterText}
clearFilterText={clearFilterText}
handleChange={handleChange}
/>
<ul>
{users.map((u: User) => (
<li key={u.id}>
<UsersModalEntry
user={u}
members={members}
setMembers={setMembers}
/>
</li>
))}
</ul>
</>
);
};

View file

@ -0,0 +1,77 @@
import React, { FC } from "react";
interface IProps {
title: string;
setTitle: React.Dispatch<React.SetStateAction<string>>;
description: string;
setDescription: React.Dispatch<React.SetStateAction<string>>;
endingDate: string;
setEndingDate: React.Dispatch<React.SetStateAction<string>>;
}
export const NewTicketForm: FC<IProps> = ({
title,
setTitle,
description,
setDescription,
endingDate,
setEndingDate
}) => {
return (
<>
<div className="row">
<div className="input-field">
<i className="material-icons prefix">note_add</i>
<input
id="title"
type="text"
className="validate"
value={title}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setTitle(e.target.value)
}
/>
<label htmlFor="title">Title</label>
</div>
<div className="input-field">
<i className="material-icons prefix">mode_edit</i>
<textarea
id="description"
className="materialize-textarea validate"
value={description}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setDescription(e.target.value)
}
></textarea>
<label htmlFor="description">Description</label>
</div>
<div className="input-field">
<i className="material-icons prefix">date_range</i>
<input
id="Due Date"
type="text"
className="datepicker"
value={endingDate}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEndingDate(e.target.value)
}
/>
<label htmlFor="Due Date">Due Date</label>
</div>
<div className="input-field">
<select id="project" className="browser-default">
<option value="" disabled selected>
Project
</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
</div>
</>
);
};

View file

@ -0,0 +1,93 @@
import React, { FC, useState, ChangeEvent, useEffect, FormEvent } from "react";
import { useParams } from "react-router-dom";
import { Modal } from "./Modal";
import { NewTicketTabRouter } from "./NewTicketTabRouter";
import { User } from "../types/User";
import { Ticket } from "../types/Ticket";
import { patch, post } from "../utils/http";
import { Constants } from "../utils/Constants";
import { Project } from "../types/Project";
import { HttpResponse } from "../types/HttpResponse";
interface IProps {
show: boolean;
handleClose(): void;
allUsers: User[];
}
export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
const [filterText, setFilterText] = useState<string>("");
const { id } = useParams();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [endingDate, setEndingDate] = useState("");
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
e: ChangeEvent<HTMLInputElement>
) => {
setFilterText(e.target.value);
};
const handleSubmit: (event: FormEvent<HTMLFormElement>) => void = async (
e: FormEvent
) => {
e.preventDefault();
let newTicket = {
title: title,
description: description,
endingDate: endingDate,
creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94",
projectId: 1
};
console.log(newTicket);
const response: HttpResponse<Ticket> = await post<Ticket>(
`${Constants.ticketsURI}`,
newTicket
);
console.log(response.parsedBody);
handleClose();
};
useEffect(() => {});
return (
<Modal show={show} handleClose={handleClose}>
<div className="row valign-wrapper indigo">
<div className="col s10">
<h4 className="white-text">New Ticket</h4>
</div>
<div className="col s2">
<i
className="right material-icons indigo lighten-3 circle"
onClick={handleClose}
>
close
</i>
</div>
</div>
<form onSubmit={handleSubmit}>
<div className="row">
<NewTicketTabRouter
tabNames={["Details", "Members"]}
users={allUsers}
title={title}
setTitle={setTitle}
description={description}
setDescription={setDescription}
endingDate={endingDate}
setEndingDate={setEndingDate}
/>
</div>
<div className="modal-footer grey lighten-3">
<input
type="submit"
className="modal-close waves-effect waves-green btn indigo"
value="Create Task"
/>
</div>
</form>
</Modal>
);
};

View file

@ -0,0 +1,55 @@
import React, { FC } from "react";
import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader";
import { NewTicketForm } from "./NewTicketForm";
import { MemberList } from "./MemberList";
import { User } from "../types/User";
import { Ticket } from "../types/Ticket";
interface IProps {
tabNames: string[];
users: User[];
description: string;
setDescription: React.Dispatch<React.SetStateAction<string>>;
title: string;
setTitle: React.Dispatch<React.SetStateAction<string>>;
endingDate: string;
setEndingDate: React.Dispatch<React.SetStateAction<string>>;
}
export const NewTicketTabRouter: FC<IProps> = ({
tabNames,
users,
description,
setDescription,
title,
setTitle,
endingDate,
setEndingDate
}) => {
const { url } = useRouteMatch();
return (
<>
<div className="row">
<TabRouterHeader tabNames={tabNames} />
<Redirect from={url} to={`${url}/details`} />
<Route path={`${url}/details`}>
<NewTicketForm
title={title}
setTitle={setTitle}
description={description}
setDescription={setDescription}
endingDate={endingDate}
setEndingDate={setEndingDate}
/>
</Route>
<Route path={`${url}/members`}>
<MemberList users={users} />
</Route>
</div>
</>
);
};

View file

@ -16,11 +16,12 @@ export const ProgressBar: FC<ProgressBarProps> = ({
remainingDays remainingDays
}) => { }) => {
const styleString: CSSProperties = { width: `${value}%` }; const styleString: CSSProperties = { width: `${value}%` };
const barColor: string = value < 75 ? "red" : "";
return ( return (
<> <>
<div className="row"> <div className="row">
<div className="progress"> <div className="progress">
<div className="determinate" style={styleString}></div> <div className={`determinate ${barColor}`} style={styleString}></div>
</div> </div>
<div> <div>
<i className="left material-icons">playlist_add_check</i> <i className="left material-icons">playlist_add_check</i>

View file

@ -0,0 +1,78 @@
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
import { Ticket } from "../types/Ticket";
import { FloatingButton } from "./FloatingButton";
import { HorizontalCard } from "./HorizontalCard";
import { FilterBar } from "./FilterBar";
import { put } from "../utils/http";
import { Constants } from "../utils/Constants";
import { HttpResponse } from "../types/HttpResponse";
import { Project } from "../types/Project";
type IProps = {
projects: Project[];
};
export const ProjectList: FC<IProps> = ({ projects }) => {
const [filterText, setFilterText] = useState<string>("");
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
setFilterText("");
};
// const archiveTicket = () => {};
const onClick: (e: MouseEvent) => void = (e: MouseEvent) => {
e.preventDefault();
setShowNew(true);
};
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
e: ChangeEvent<HTMLInputElement>
) => {
setFilterText(e.target.value);
};
const [showNew, setShowNew] = useState(false);
let filteredTickets = projects.filter(
t =>
t.status !== "Done" &&
t.title.toLowerCase().includes(filterText.toLowerCase())
);
return (
<>
<div className="row valign-wrapper">
<h3>Projects</h3>
<FloatingButton
color="indigo lighten-1"
size="small"
onClick={onClick}
/>
<FilterBar
filterText={filterText}
handleChange={handleChange}
clearFilterText={clearFilterText}
/>
</div>
<div className="col s12 grey">
<ul>
{filteredTickets.length === 0 ? (
<HorizontalCard />
) : (
filteredTickets.map((t: Project) => (
<HorizontalCard
key={t.id}
title={t.title}
remainingDays={t.endingDate}
validateTicket={async (e: MouseEvent) => {
e.preventDefault();
await put<HttpResponse<Ticket>>(
`${Constants.ticketsURI}/${t.id}/closed`,
{}
);
}}
// archiveTicket={archiveTicket}
/>
))
)}
</ul>
</div>
</>
);
};

View file

@ -1,12 +1,13 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader"; import { TabRouterHeader } from "./TabRouterHeader";
import { TicketList } from "./TicketList"; import { TicketList } from "./TicketList";
import { FileList } from "./AppFileList"; import { FileList } from "./AppFileList";
import { ActivityList } from "./ActivityList";
import { Ticket } from "../types/Ticket"; import { Ticket } from "../types/Ticket";
import { AppFile } from "../types/AppFile"; import { AppFile } from "../types/AppFile";
import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { ActivityList } from "./ActivityList";
import { Activity } from "../types/Activity"; import { Activity } from "../types/Activity";
import { User } from "../types/User";
interface IProps { interface IProps {
tickets: Ticket[]; tickets: Ticket[];
@ -14,15 +15,18 @@ interface IProps {
tabNames: string[]; tabNames: string[];
files: AppFile[]; files: AppFile[];
activities: Activity[]; activities: Activity[];
allUsers: User[];
} }
export const TabRouter: FC<IProps> = ({ export const TabRouter: FC<IProps> = ({
tickets, tickets,
tabNames, tabNames,
files, files,
activities activities,
allUsers
}) => { }) => {
const { url } = useRouteMatch(); const { url } = useRouteMatch();
return ( return (
<> <>
<div className="row"> <div className="row">
@ -31,7 +35,7 @@ export const TabRouter: FC<IProps> = ({
<Redirect from={url} to={`${url}/tickets`} /> <Redirect from={url} to={`${url}/tickets`} />
<Route path={`${url}/tickets`}> <Route path={`${url}/tickets`}>
<TicketList tickets={tickets} /> <TicketList tickets={tickets} users={allUsers} />
</Route> </Route>
<Route path={`${url}/files`}> <Route path={`${url}/files`}>

View file

@ -7,14 +7,15 @@ interface IProps {
} }
export const TabRouterHeader: FC<IProps> = ({ export const TabRouterHeader: FC<IProps> = ({
tabClass = "tab col s4", tabNames,
tabNames tabClass = `tab col s${12 / tabNames.length}`
}) => { }) => {
const [isActive, setIsActive] = useState(0); const [isActive, setIsActive] = useState(0);
const nTabs = tabNames.length; const nTabs = tabNames.length;
return ( return (
<> <>
<ul className="tabs z-depth-1"> <ul className="tabs">
{tabNames.map((name, i) => ( {tabNames.map((name, i) => (
<TabUnit <TabUnit
key={i} key={i}
@ -27,7 +28,7 @@ export const TabRouterHeader: FC<IProps> = ({
/> />
))} ))}
<li <li
className="indicator" className="indicator indigo lighten-2"
style={{ style={{
left: `${(isActive / nTabs) * 100}%`, left: `${(isActive / nTabs) * 100}%`,
right: `${(1 - (isActive + 1) / nTabs) * 100}%` right: `${(1 - (isActive + 1) / nTabs) * 100}%`
@ -68,7 +69,11 @@ const TabUnit: FC<TabUnitProps> = ({
<Link <Link
to={`${url}/${text}`} to={`${url}/${text}`}
id={value} id={value}
className={isActive === parseInt(value) ? "active pink lighten-5" : ""} className={
isActive === parseInt(value)
? "active indigo lighten-5 indigo-text"
: "indigo-text"
}
onClick={() => setIsActive(parseInt(value))} onClick={() => setIsActive(parseInt(value))}
> >
{text} {text}

View file

@ -3,20 +3,27 @@ import { Ticket } from "../types/Ticket";
import { FloatingButton } from "./FloatingButton"; import { FloatingButton } from "./FloatingButton";
import { HorizontalCard } from "./HorizontalCard"; import { HorizontalCard } from "./HorizontalCard";
import { FilterBar } from "./FilterBar"; import { FilterBar } from "./FilterBar";
import { User } from "../types/User";
import { HttpResponse } from "../types/HttpResponse";
import { put } from "../utils/http";
import { Constants } from "../utils/Constants";
import { NewTicketModal } from "./NewTicketModal";
type TicketListProps = { type TicketListProps = {
tickets: Ticket[]; tickets: Ticket[];
users: User[];
}; };
export const TicketList: FC<TicketListProps> = ({ tickets }) => { export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
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 archiveTicket = () => {}; // const archiveTicket = () => {};
const validateTicket = () => {};
const onClick: (e: MouseEvent) => void = (e: MouseEvent) => { const onClick: (e: MouseEvent) => void = (e: MouseEvent) => {
e.preventDefault(); e.preventDefault();
setShowNew(true);
}; };
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = ( const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
e: ChangeEvent<HTMLInputElement> e: ChangeEvent<HTMLInputElement>
@ -24,12 +31,25 @@ export const TicketList: FC<TicketListProps> = ({ tickets }) => {
setFilterText(e.target.value); setFilterText(e.target.value);
}; };
const [showNew, setShowNew] = useState(false);
let filteredTickets = tickets.filter(
t =>
t.status !== "Done" &&
t.title.toLowerCase().includes(filterText.toLowerCase())
);
return ( return (
<> <>
<div className="row valign-wrapper"> <div className="row valign-wrapper">
<NewTicketModal
handleClose={() => {
setShowNew(false);
}}
show={showNew}
allUsers={users}
/>
<h3>Tickets</h3> <h3>Tickets</h3>
<FloatingButton <FloatingButton
color=" blue-grey lighten-4" color="indigo lighten-3"
size="small" size="small"
onClick={onClick} onClick={onClick}
/> />
@ -41,20 +61,25 @@ export const TicketList: FC<TicketListProps> = ({ tickets }) => {
</div> </div>
<div className="col s12 grey"> <div className="col s12 grey">
<ul> <ul>
{tickets {filteredTickets.length === 0 ? (
.filter(t => <HorizontalCard />
t.title.toLowerCase().includes(filterText.toLowerCase()) ) : (
) filteredTickets.map((t: Ticket) => (
.map((t: Ticket) => ( <HorizontalCard
<li key={t.id}> key={t.id}
<HorizontalCard title={t.title}
title={t.title} remainingDays={t.endingDate}
remainingDays={t.plannedEnding} validateTicket={async (e: MouseEvent) => {
validateTicket={validateTicket} e.preventDefault();
archiveTicket={archiveTicket} await put<HttpResponse<Ticket>>(
/> `${Constants.ticketsURI}/${t.id}/closed`,
</li> {}
))} );
}}
// archiveTicket={archiveTicket}
/>
))
)}
</ul> </ul>
</div> </div>
</> </>

View file

@ -0,0 +1,21 @@
import React, { FC } from "react";
import { Header } from "../components/Header";
import { Avatar } from "../components/Avatar";
interface IProps {
fullName: string;
presentation: string;
picture: string;
}
export const UserHeader: FC<IProps> = ({ fullName, presentation, picture }) => {
return (
<div className="row valign-wrapper">
<div className="col s2">
<Avatar picture={picture} />
</div>
<div className="col s10">
<Header title={fullName} description={presentation} />
</div>
</div>
);
};

View file

@ -0,0 +1,34 @@
import React, { FC } from "react";
import { Route, useRouteMatch, Redirect } from "react-router-dom";
import { TabRouterHeader } from "./TabRouterHeader";
import { ProjectList } from "./ProjectList";
import { Ticket } from "../types/Ticket";
import { Project } from "../types/Project";
interface IProps {
tabNames: string[];
tickets: Ticket[];
projects: Project[];
}
export const UserTabRouter: FC<IProps> = ({ tickets, tabNames, projects }) => {
const { url } = useRouteMatch();
return (
<>
<div className="row">
<TabRouterHeader tabNames={tabNames} />
<Redirect from={url} to={`${url}/projects`} />
<Route path={`${url}/projects`}>
<ProjectList projects={projects} />
</Route>
{/* <Route path={`${url}/tickets`}>
<TicketList tickets={tickets} />
</Route> */}
</div>
</>
);
};

View file

@ -35,22 +35,22 @@ export const UsersModal: FC<IProps> = ({
e: FormEvent e: FormEvent
) => { ) => {
e.preventDefault(); e.preventDefault();
await patch<User[]>( await patch<User[]>(
`${Constants.projectsURI}/${id}/members`, `${Constants.projectsURI}/${id}/members`,
members.map(m => m.id) members.map(m => m.id)
); );
handleClose();
}; };
return ( return (
<Modal show={show} handleClose={handleClose}> <Modal show={show} handleClose={handleClose}>
<div className="row valign-wrapper blue"> <div className="row valign-wrapper indigo">
<div className="col s10"> <div className="col s10">
<h4 className="white-text">Manage users</h4> <h4 className="white-text">Manage users</h4>
</div> </div>
<div className="col s2"> <div className="col s2">
<i <i
className="right material-icons blue lighten-3 circle" className="right material-icons indigo lighten-3 circle"
onClick={handleClose} onClick={handleClose}
> >
close close
@ -78,10 +78,10 @@ export const UsersModal: FC<IProps> = ({
</li> </li>
))} ))}
</ul> </ul>
<div className="modal-footer"> <div className="modal-footer grey lighten-3">
<input <input
type="submit" type="submit"
className="modal-close waves-effect waves-green btn" className="modal-close waves-effect waves-green btn indigo"
value="Done" value="Done"
/> />
</div> </div>

View file

@ -8,7 +8,6 @@ interface IProps {
} }
export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => { export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
// console.log(members);
const match: (id: string) => boolean = (id: string) => { const match: (id: string) => boolean = (id: string) => {
return Boolean(members.find(m => m.id === id)); return Boolean(members.find(m => m.id === id));
}; };

View file

@ -1,6 +1,90 @@
import React, { FC } from "react"; import React, { FC, useState, useEffect } from "react";
import { UserPage } from "../pages/UserPage"; import { UserPage } from "../pages/UserPage";
import { UserVM } from "../VM/UserVM";
import { User } from "../types/User";
import { AppFile } from "../types/AppFile";
import { Activity } from "../types/Activity";
import { Ticket } from "../types/Ticket";
import { Preloader } from "../components/Preloader";
export const UserController: FC = () => { export const UserController: FC = () => {
return <UserPage />; const [isLoading, setIsLoading] = useState(true);
const user: User = {
id: "resldsm,dgd",
firstName: "David",
lastName: "Whittaker",
fullName: "David Whittaker",
presentation: "Interface designer and front-end developer",
creationDate: new Date().toDateString(),
email: "dw@mail.au",
phone: "0998765432",
picture: require("../images/user_1.jpg"),
projects: [
{
id: 1,
title: "Project Title",
description: "What is it about",
progression: 25,
creationDate: new Date().toDateString(),
endingDate: "2020-02-17 15:51:02.787373",
status: "Todo",
manager: {} as User,
users: [] as User[],
tickets: [] as Ticket[],
files: [] as AppFile[],
activities: [] as Activity[]
}
],
tickets: [
{
id: 1,
title: "Client objective meeting",
description: "Client objective meeting",
endingDate: "2020-02-17 15:51:02.787373",
status: "Done",
project: {
id: 1,
title: "Project Title",
description: "What is it about",
progression: 25,
creationDate: new Date().toDateString(),
endingDate: "2020-02-17 15:51:02.787373",
status: "Todo",
manager: {} as User,
users: [] as User[],
tickets: [] as Ticket[],
files: [] as AppFile[],
activities: [] as Activity[]
}
},
{
id: 2,
title: "Assemble Outcomes Report for client",
description: "Assemble Outcomes Report for client",
endingDate: "2020-02-27 15:51:02.787373",
status: "To Do",
project: {
id: 1,
title: "Project Title",
description: "What is it about",
progression: 25,
creationDate: new Date().toDateString(),
endingDate: "2020-02-17 15:51:02.787373",
status: "Todo",
manager: {} as User,
users: [] as User[],
tickets: [] as Ticket[],
files: [] as AppFile[],
activities: [] as Activity[]
}
}
],
activities: []
};
useEffect(() => {
setTimeout(() => setIsLoading(false), 1000);
});
const viewModel = new UserVM(user);
return isLoading ? <Preloader /> : <UserPage viewModel={viewModel} />;
}; };

View file

@ -37,7 +37,7 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
<AvatarList users={users} /> <AvatarList users={users} />
<FloatingButton <FloatingButton
icon="add" icon="add"
color="grey" color="indigo lighten-3"
size="small" size="small"
onClick={() => setShowModal(true)} onClick={() => setShowModal(true)}
/> />
@ -59,6 +59,7 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
tickets={tickets} tickets={tickets}
files={files} files={files}
activities={activities} activities={activities}
allUsers={allUsers}
/> />
</div> </div>
</div> </div>

View file

@ -1,12 +1,32 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Header } from "../components/Header"; import { UserVM } from "../VM/UserVM";
import { UserHeader } from "../components/UserHeader";
import { UserTabRouter } from "../components/UserTabRouter";
export const UserPage: FC = () => { interface IProps {
viewModel: UserVM;
}
export const UserPage: FC<IProps> = ({ viewModel }) => {
const { fullName, presentation, picture, projects, tickets } = viewModel;
const tabNames: string[] = ["Projects", "Tickets"];
return ( return (
<Header title = "Brand Concept and Design" description = "Research, ideate and present brand concepts for client consideration"/> <div className="section">
// <TabView> <div className="container">
<UserHeader
picture={picture}
fullName={fullName}
presentation={presentation}
/>
<UserTabRouter
tabNames={tabNames}
projects={projects}
tickets={tickets}
/>
</div>
{/* // <TabView>
// <CardList> // <CardList>
// <CardList> // <CardList>
// </TabView> // </TabView> */}
</div>
); );
}; };

View file

@ -1,7 +1,10 @@
import { Project } from "./Project";
export interface Ticket { export interface Ticket {
id: number; id: number;
title: string; title: string;
description: string; description: string;
status: string; status: string;
plannedEnding: string; endingDate: string;
project: Project;
} }

View file

@ -10,7 +10,7 @@ export interface User {
presentation: string; presentation: string;
email: string; email: string;
phone: string; phone: string;
createdAt: string; creationDate: string;
picture: string; picture: string;
activities: Activity[]; activities: Activity[];
projects: Project[]; projects: Project[];

View file

@ -12,7 +12,7 @@ import * as creacteHistory from "history";
import { ProjectController } from "../controllers/ProjectController"; import { ProjectController } from "../controllers/ProjectController";
import { NotFoundPage } from "../pages/NotFoundPage"; import { NotFoundPage } from "../pages/NotFoundPage";
import { TestPage } from "../pages/TestPage"; import { TestPage } from "../pages/TestPage";
// import { UserController } from "../controllers/UserController"; import { UserController } from "../controllers/UserController";
// import { TicketController } from "../controllers/TicketController"; // import { TicketController } from "../controllers/TicketController";
export const history = creacteHistory.createBrowserHistory(); export const history = creacteHistory.createBrowserHistory();
@ -20,7 +20,7 @@ export const history = creacteHistory.createBrowserHistory();
export const AppRouter = () => { export const AppRouter = () => {
return ( return (
<Router history={history}> <Router history={history}>
<div className="grey lighten-4"> <div className="grey lighten-3">
<Switch> <Switch>
<Route exact path="/"> <Route exact path="/">
<TestPage /> <TestPage />
@ -28,24 +28,20 @@ export const AppRouter = () => {
{/* <Route path="/"> {/* <Route path="/">
<HomeController /> <HomeController />
</Route> </Route> */}
<Route path="/users/:id"> <Route path="/users/:id">
<UserController /> <UserController />
</Route> */} </Route>
<Route path="/projects/:id"> <Route path="/projects/:id">
<ProjectController /> <ProjectController />
</Route> </Route>
{/* <Route path="/tickets/:id"> {/* <Route path="/tickets/:id">
<TicketController /> <TicketController />
</Route> */} </Route> */}
<Route path="/401"> <Route path="/401">
<NotFoundPage /> <NotFoundPage />
</Route> </Route>
{/* <Route path="*">
<Redirect to="/error" />
</Route> */}
</Switch> </Switch>
</div> </div>
</Router> </Router>