From 37ce0fb54715b80357045d28e4670dd4a8a2b1da Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Thu, 12 Mar 2020 22:18:17 +0100 Subject: [PATCH] User Page --- Controllers/AppUsersController.cs | 21 ++- Models/AppUser.cs | 5 +- README.md | 3 +- client/src/VM/UserVM.ts | 15 ++ client/src/components/HorizontalCard.tsx | 6 +- client/src/components/NewTicketModal.tsx | 13 +- client/src/components/ProjectList.tsx | 16 +- client/src/components/TicketList.tsx | 19 +- client/src/components/UserTabRouter.tsx | 2 +- client/src/controllers/ProjectController.tsx | 1 + client/src/controllers/UserController.tsx | 188 +++++++++++-------- client/src/utils/http.ts | 2 +- 12 files changed, 171 insertions(+), 120 deletions(-) diff --git a/Controllers/AppUsersController.cs b/Controllers/AppUsersController.cs index f93ade2..2c1b759 100644 --- a/Controllers/AppUsersController.cs +++ b/Controllers/AppUsersController.cs @@ -43,6 +43,7 @@ namespace TicketManager.Controllers return await _context.AppUsers .Include(u => u.Assignments) .ThenInclude(a => a.Project) + .ThenInclude(p => p.Tickets) .Include(u => u.Activities) .AsNoTracking() .Select(u => new AppUserDTO(u)) @@ -68,6 +69,7 @@ namespace TicketManager.Controllers var user = await _context.AppUsers .Include(u => u.Assignments) .ThenInclude(a => a.Project) + .ThenInclude(p => p.Tickets) .Include(u => u.Activities) .AsNoTracking() .FirstOrDefaultAsync(u => u.Id == id); @@ -80,12 +82,12 @@ namespace TicketManager.Controllers } /// - /// Updates the specific project with Id. + /// Updates the specific user with Id. /// /// /// Sample request: /// - /// PUT: api/v1/Projects/3 + /// PUT: api/v1/Users/3 /// { /// "id": "357727fd-5262-4522-b8a3-38271d43de84", /// "firstName": "Thomas", @@ -97,7 +99,7 @@ namespace TicketManager.Controllers /// /// /// Request was succesful but no content is changed - /// If the required project is null + /// If the required User is null [HttpPut("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -129,12 +131,12 @@ namespace TicketManager.Controllers } /// - /// Creates a project. + /// Creates a User. /// /// /// Sample request: /// - /// POST: api/v1/Projects/ + /// POST: api/v1/Users/ /// { /// "firstName": "Thomas", /// "lastName": "Price", @@ -144,7 +146,7 @@ namespace TicketManager.Controllers /// } /// /// - /// Returns the created project + /// Returns the created User [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -174,15 +176,15 @@ namespace TicketManager.Controllers } /// - /// Deletes the project identified by its Id + /// Deletes the User identified by its Id /// /// /// Sample request: /// - /// DELETE: api/v1/Projects/5 + /// DELETE: api/v1/Users/5 /// /// - /// Returns the deleted project + /// Returns the deleted User [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [HttpDelete("{id}")] @@ -205,6 +207,7 @@ namespace TicketManager.Controllers var user = await _context.AppUsers .Include(u => u.Assignments) .ThenInclude(a => a.Project) + .ThenInclude(p => p.Tickets) .AsNoTracking() .FirstOrDefaultAsync(u => u.Id == id); if (user == null) diff --git a/Models/AppUser.cs b/Models/AppUser.cs index 9dd2315..43e0924 100644 --- a/Models/AppUser.cs +++ b/Models/AppUser.cs @@ -55,7 +55,10 @@ namespace TicketManager.Models public List GetTickets() { var tickets = new List(); - GetProjects().ForEach(p => tickets.Concat(p.Tickets)); + foreach (var p in GetProjects()) + { + tickets.AddRange(p.Tickets); + } return tickets; } } diff --git a/README.md b/README.md index 4980c9f..fb4ca2d 100644 --- a/README.md +++ b/README.md @@ -50,4 +50,5 @@ - [x] write dtos without circular dependencies. - [ ] use dtoRequest for PutProjects - [ ] render avatarlist after UserModal Update -- [ ] Form validators +- [x] Form validators +- [ ] Azure diff --git a/client/src/VM/UserVM.ts b/client/src/VM/UserVM.ts index 175da9a..5152faf 100644 --- a/client/src/VM/UserVM.ts +++ b/client/src/VM/UserVM.ts @@ -1,19 +1,34 @@ import { Project } from "../types/Project"; import { Ticket } from "../types/Ticket"; import { User } from "../types/User"; +import { Activity } from "../types/Activity"; export class UserVM { + public id: string; + public firstName: string; + public lastName: string; public fullName: string; public presentation: string; + public email: string; + public phone: string; + public creationDate: string; public picture: string; public projects: Project[]; public tickets: Ticket[]; + public activities: Activity[]; public constructor(user: User) { + this.id = user.id; + this.firstName = user.firstName; + this.lastName = user.lastName; this.fullName = user.fullName; this.presentation = user.presentation; + this.email = user.email; + this.phone = user.phone; + this.creationDate = user.creationDate; this.picture = user.picture; this.projects = user.projects; this.tickets = user.tickets; + this.activities = user.activities; } } diff --git a/client/src/components/HorizontalCard.tsx b/client/src/components/HorizontalCard.tsx index 815804c..8d53495 100644 --- a/client/src/components/HorizontalCard.tsx +++ b/client/src/components/HorizontalCard.tsx @@ -6,13 +6,13 @@ interface IProps { title?: string; remainingDays?: string; validateTicket?: (event: MouseEvent) => void; - // archiveTicket: (event: MouseEvent) => void; + link?: string; } export const HorizontalCard: FC = ({ title, remainingDays, - // archiveTicket, + link = "#", validateTicket }) => { return ( @@ -23,7 +23,7 @@ export const HorizontalCard: FC = ({
- + {title ?? "Nothing to do"}
diff --git a/client/src/components/NewTicketModal.tsx b/client/src/components/NewTicketModal.tsx index 03c7ccd..055974f 100644 --- a/client/src/components/NewTicketModal.tsx +++ b/client/src/components/NewTicketModal.tsx @@ -1,12 +1,12 @@ import React, { FC, useState, FormEvent } from "react"; -import { useParams, useRouteMatch } from "react-router-dom"; +import { useRouteMatch } from "react-router-dom"; import { Modal } from "./Modal"; import { NewTicketForm } from "./NewTicketForm"; import { Ticket } from "../types/Ticket"; import { Project } from "../types/Project"; import { post } from "../utils/http"; import { Constants } from "../utils/Constants"; -import { HttpResponse } from "../types/HttpResponse"; +// import { HttpResponse } from "../types/HttpResponse"; interface IProps { show: boolean; @@ -38,12 +38,9 @@ export const NewTicketModal: FC = ({ creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94", projectId: parseInt(projectId) }; - // console.log(newTicket); - const response: HttpResponse = await post( - `${Constants.ticketsURI}`, - newTicket - ); - // console.log(response.parsedBody); + + // const response: HttpResponse = + await post(`${Constants.ticketsURI}`, newTicket); handleClose(); }; diff --git a/client/src/components/ProjectList.tsx b/client/src/components/ProjectList.tsx index 85d1460..a574d41 100644 --- a/client/src/components/ProjectList.tsx +++ b/client/src/components/ProjectList.tsx @@ -1,6 +1,5 @@ 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"; @@ -17,19 +16,13 @@ export const ProjectList: FC = ({ projects }) => { 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) => void = ( e: ChangeEvent ) => { setFilterText(e.target.value); }; - const [showNew, setShowNew] = useState(false); let filteredTickets = projects.filter( t => t.status !== "Done" && @@ -39,18 +32,13 @@ export const ProjectList: FC = ({ projects }) => { <>

Projects

-
-
+
    {filteredTickets.length === 0 ? ( @@ -60,6 +48,7 @@ export const ProjectList: FC = ({ projects }) => { key={t.id} title={t.title} remainingDays={t.endingDate} + link={`/projects/${t.id}`} validateTicket={async (e: MouseEvent) => { e.preventDefault(); await put>( @@ -67,7 +56,6 @@ export const ProjectList: FC = ({ projects }) => { {} ); }} - // archiveTicket={archiveTicket} /> )) )} diff --git a/client/src/components/TicketList.tsx b/client/src/components/TicketList.tsx index 677af17..a2a5ad0 100644 --- a/client/src/components/TicketList.tsx +++ b/client/src/components/TicketList.tsx @@ -12,9 +12,14 @@ import { Project } from "../types/Project"; type TicketListProps = { tickets: Ticket[]; allProjects: Project[]; + addButton?: Boolean; }; -export const TicketList: FC = ({ tickets, allProjects }) => { +export const TicketList: FC = ({ + tickets, + allProjects, + addButton = true +}) => { const [filterText, setFilterText] = useState(""); const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { setFilterText(""); @@ -47,11 +52,13 @@ export const TicketList: FC = ({ tickets, allProjects }) => { allProjects={allProjects} />

    Tickets

    - + {addButton && ( + + )} = ({ tickets, tabNames, projects }) => { - +
diff --git a/client/src/controllers/ProjectController.tsx b/client/src/controllers/ProjectController.tsx index 3d30d04..427bd8f 100644 --- a/client/src/controllers/ProjectController.tsx +++ b/client/src/controllers/ProjectController.tsx @@ -77,6 +77,7 @@ export const ProjectController: FC = () => { if (hasError) { return ; } + const viewModel = new ProjectVM(project, allUsers, allProjects); return isLoading ? : ; }; diff --git a/client/src/controllers/UserController.tsx b/client/src/controllers/UserController.tsx index afc337b..4fa23c9 100644 --- a/client/src/controllers/UserController.tsx +++ b/client/src/controllers/UserController.tsx @@ -1,90 +1,126 @@ import React, { FC, useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; 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 { HttpResponse } from "../types/HttpResponse"; import { Preloader } from "../components/Preloader"; +import { get } from "../utils/http"; +import { Constants } from "../utils/Constants"; +import { ErrorController } from "./ErrorController"; +// import { AppFile } from "../types/AppFile"; +// import { Activity } from "../types/Activity"; +// import { Ticket } from "../types/Ticket"; export const UserController: FC = () => { const [isLoading, setIsLoading] = useState(true); + const [user, setUser] = useState({} as User); + const [hasError, setHasError] = useState(false); + const [error, setError] = useState(""); + const { id } = useParams(); - 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[] + async function httpGetUser(id: string): Promise { + try { + const response: HttpResponse = await get( + `${Constants.usersURI}/${id}` + ); + if (response.parsedBody !== undefined) { + setUser(response.parsedBody); + setIsLoading(false); } - ], - 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: [] - }; + } catch (ex) { + console.error(ex); + setHasError(true); + setError(ex); + } + } + + // const user: User = { + // id: "resldsm,dgd", + // firstName: "Ti", + // lastName: "Nyny", + // fullName: "Nilka Netty Nemo", + // presentation: "Woman of my life ❤️❤️❤️", + // creationDate: new Date().toDateString(), + // email: "dw@mail.au", + // phone: "0998765432", + // picture: require("../images/user_1.jpg"), + // projects: [ + // { + // id: 1, + // title: "OP Baby", + // 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); - }); + if (id !== undefined) { + httpGetUser(id); + } else { + setHasError(true); + setError("Bad Request"); + } + }, [id]); + + if (hasError) { + return ; + } + const viewModel = new UserVM(user); return isLoading ? : ; }; diff --git a/client/src/utils/http.ts b/client/src/utils/http.ts index d8f02fc..e73965e 100644 --- a/client/src/utils/http.ts +++ b/client/src/utils/http.ts @@ -58,5 +58,5 @@ const headers: Headers = new Headers({ Accept: "application/json", "Content-Type": "application/json", Authorization: - "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4MzI0ODU1NSwiZXhwIjoxNTgzMzM0OTU1LCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.lfgZAwqq3VHNAM1Y33Vj7Jtal6IdN92qWJHwu5T8l-56kBXNC2g1pm6TshRhYDKmedoJmSddjKXClA9eLu1Ve1X8wLj7CbrhZtGNsOcIEStF9icahCy8y2OdP2U6UXJJt9HehwqvwT3JltH_MqyYeJsyMsah3a3rlu6LoEAHNqF4jk8RUxZKjlVJz_iW-tbJ_GoGYrTp_bgvw6IBpgZvJDPGveaA6ms20CoN4zpXaL2ucgtaRbasXfD-z4NDwYN5_9TRPNcf9cuxViZ28CJ66uWxK8BEKysWnABYkh239Q71K2t41hFvQV8ti5l1UKcuqFf_lUGo0wYo9F-MGK6RKA" + "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4NDAzNzQyMCwiZXhwIjoxNTg0MTIzODIwLCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.hbn9fRaMYZuvapjCWB0cvwWlEZ6oDoN8nPN8cSkX720VNHYg4JxwVRf4wnf5nOMLUUEIbUudmkBeCZSeW87SQUi41rEHnB9xDXxbVTiEkmyy1yiZUdsDc9Z_hi1nCzKtrbTRmSA6Uo8aHFF2hBGK2_QUcY6hx3Dqx3OYeNbAVx0xjp5OV4XvhVqA9TH3PP_OziJ9NtZAgGzn8pBRlbuTP0ZXL4pXvbcDNcnInx8r5xT_YYxdGqWBgpSqTrfv5t0fUrnjr4ICW49zSBHhA2Nee456c-hlijJ6ufsFcbqegsN7DkFcpWysrY943bIqXJK9XhZlFnz7kBK2nf8culwtmw" });