mirror of
https://github.com/rjNemo/ticket_manager
synced 2026-06-06 00:36:39 +00:00
Merge branch 'react' of https://github.com/rjNemo/ticket_manager
This commit is contained in:
commit
5f33789dc2
31 changed files with 367 additions and 120 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -35,3 +35,5 @@ client/.env.production.local
|
|||
client/npm-debug.log*
|
||||
client/yarn-debug.log*
|
||||
client/yarn-error.log*
|
||||
|
||||
client/src/authentication/config.json
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
- [ ] error page redirect when offline.
|
||||
- [x] ticket/files/activities list placeholders when empty
|
||||
- [ ] 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
|
||||
- [ ] render avatarlist after UserModal Update
|
||||
- [ ] Form validators
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ namespace TicketManager.Resources
|
|||
{
|
||||
public class NewAppUserDTO
|
||||
{
|
||||
[Required]
|
||||
public string FirstName { get; set; }
|
||||
public string LastName { get; set; }
|
||||
public string Presentation { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.EmailAddress)]
|
||||
public string Email { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ namespace TicketManager.Resources
|
|||
[Required]
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
[Required]
|
||||
public DateTime EndingDate { get; set; }
|
||||
[Required]
|
||||
public Guid ManagerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,11 @@ namespace TicketManager.Resources
|
|||
{
|
||||
public class NewTicketDTO
|
||||
{
|
||||
[Required]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime EndingDate { get; set; }
|
||||
|
||||
|
|
@ -20,8 +21,9 @@ namespace TicketManager.Resources
|
|||
public string Difficulty { get; set; }
|
||||
|
||||
public string Category { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid CreatorId { get; set; }
|
||||
[Required]
|
||||
public int ProjectId { get; set; }
|
||||
}
|
||||
}
|
||||
43
client/package-lock.json
generated
43
client/package-lock.json
generated
|
|
@ -4,6 +4,19 @@
|
|||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.6.4.tgz",
|
||||
"integrity": "sha512-zKUfUxOHL/YwSedP1BkKKRRrB4AJ7whMKBVlf6M7dTvVzeCrRzTWEXUF+HB4/iG4skVRyr+U35qQzMYoFWCxhw==",
|
||||
"requires": {
|
||||
"browser-tabs-lock": "^1.2.1",
|
||||
"core-js": "^3.2.1",
|
||||
"es-cookie": "^1.2.0",
|
||||
"fast-text-encoding": "^1.0.0",
|
||||
"promise-polyfill": "^8.1.3",
|
||||
"unfetch": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
|
||||
|
|
@ -1441,6 +1454,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz",
|
||||
"integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA=="
|
||||
},
|
||||
"@types/auth0": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/auth0/-/auth0-2.20.0.tgz",
|
||||
"integrity": "sha512-dIb3VeVD6fSrGMUBWVHcb4N4trxC8BfSNkzqpafhhwsXDiJgeb4GQTqN4b+7Pafyq7vRVpeOoaS4gq9ovT+YwQ=="
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
|
||||
|
|
@ -2702,6 +2720,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"browser-tabs-lock": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.7.tgz",
|
||||
"integrity": "sha512-2H3EX1YstZEI8mkmoKPo7cIUfc0lJe2nysFQjcY4wRk/Z6Mb6jnDPYEloQNP0LQuBe4J7M1T78+sFE0gZagHDw=="
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
|
|
@ -4315,6 +4338,11 @@
|
|||
"string.prototype.trimright": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"es-cookie": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz",
|
||||
"integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q=="
|
||||
},
|
||||
"es-to-primitive": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||
|
|
@ -5171,6 +5199,11 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
|
||||
},
|
||||
"fast-text-encoding": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz",
|
||||
"integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ=="
|
||||
},
|
||||
"faye-websocket": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
|
||||
|
|
@ -10219,6 +10252,11 @@
|
|||
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
|
||||
},
|
||||
"promise-polyfill": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz",
|
||||
"integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g=="
|
||||
},
|
||||
"prompts": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz",
|
||||
|
|
@ -12549,6 +12587,11 @@
|
|||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz",
|
||||
"integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ=="
|
||||
},
|
||||
"unfetch": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
|
||||
"integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg=="
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": "^1.6.4",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.4.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"@types/auth0": "^2.20.0",
|
||||
"@types/jest": "^24.9.1",
|
||||
"@types/node": "^12.12.26",
|
||||
"@types/react": "^16.9.19",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
import React, { FC } from "react";
|
||||
import Layout from "./pages/Layout";
|
||||
import { useAuth0 } from "./authentication/auth0";
|
||||
|
||||
const App: FC = () => {
|
||||
return <Layout />;
|
||||
const { loading } = useAuth0();
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<Layout />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { User } from "../types/User";
|
|||
import { getRemainingdays } from "../utils/methods";
|
||||
|
||||
export default class ProjectVM {
|
||||
// public id: number;
|
||||
public id: number;
|
||||
public title: string;
|
||||
public description: string;
|
||||
public creationDate: string;
|
||||
|
|
@ -22,9 +22,14 @@ export default class ProjectVM {
|
|||
public ticketsTotalCount: number;
|
||||
public ticketsDone: number;
|
||||
public remainingDays: number;
|
||||
public allProjects: Project[];
|
||||
|
||||
public constructor(project: Project, allUsers: User[]) {
|
||||
// this.id = project.id;
|
||||
public constructor(
|
||||
project: Project,
|
||||
allUsers: User[],
|
||||
allProjects: Project[]
|
||||
) {
|
||||
this.id = project.id;
|
||||
this.title = project.title;
|
||||
this.description = project.description;
|
||||
this.creationDate = project.creationDate;
|
||||
|
|
@ -44,5 +49,6 @@ export default class ProjectVM {
|
|||
? 0
|
||||
: this.tickets.filter(t => t.status === "Done").length;
|
||||
this.remainingDays = getRemainingdays(project.endingDate);
|
||||
this.allProjects = allProjects;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
112
client/src/authentication/auth0.tsx
Normal file
112
client/src/authentication/auth0.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import React, { useState, useEffect, useContext, FC } from "react";
|
||||
import createAuth0Client from "@auth0/auth0-spa-js";
|
||||
import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client";
|
||||
|
||||
interface IAuth0Context {
|
||||
isAuthenticated: boolean;
|
||||
user: any;
|
||||
loading: boolean;
|
||||
popupOpen: boolean;
|
||||
loginWithPopup(options: PopupLoginOptions): Promise<void>;
|
||||
handleRedirectCallback(): Promise<RedirectLoginResult>;
|
||||
getIdTokenClaims(o?: getIdTokenClaimsOptions): Promise<IdToken>;
|
||||
loginWithRedirect(o: RedirectLoginOptions): Promise<void>;
|
||||
getTokenSilently(o?: GetTokenSilentlyOptions): Promise<string | undefined>;
|
||||
getTokenWithPopup(o?: GetTokenWithPopupOptions): Promise<string | undefined>;
|
||||
logout(o?: LogoutOptions): void;
|
||||
}
|
||||
export interface IAuth0ProviderOptions {
|
||||
children: React.ReactElement;
|
||||
onRedirectCallback?(result: RedirectLoginResult): void;
|
||||
}
|
||||
|
||||
const DEFAULT_REDIRECT_CALLBACK: () => void = () =>
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
|
||||
export const Auth0Context: React.Context<IAuth0Context | null> = React.createContext<IAuth0Context | null>(
|
||||
null
|
||||
);
|
||||
export const useAuth0: () => IAuth0Context = () => useContext(Auth0Context)!;
|
||||
export const Auth0Provider = ({
|
||||
children,
|
||||
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
|
||||
...initOptions
|
||||
}: IAuth0ProviderOptions & Auth0ClientOptions) => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [user, setUser] = useState();
|
||||
const [auth0Client, setAuth0] = useState<Auth0Client>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [popupOpen, setPopupOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initAuth0 = async () => {
|
||||
const auth0FromHook = await createAuth0Client(initOptions);
|
||||
setAuth0(auth0FromHook);
|
||||
|
||||
if (window.location.search.includes("code=")) {
|
||||
const { appState } = await auth0FromHook.handleRedirectCallback();
|
||||
onRedirectCallback(appState);
|
||||
}
|
||||
|
||||
const isAuthenticated = await auth0FromHook.isAuthenticated();
|
||||
|
||||
setIsAuthenticated(isAuthenticated);
|
||||
|
||||
if (isAuthenticated) {
|
||||
const user = await auth0FromHook.getUser();
|
||||
setUser(user);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
initAuth0();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const loginWithPopup = async (o: PopupLoginOptions) => {
|
||||
setPopupOpen(true);
|
||||
try {
|
||||
await auth0Client!.loginWithPopup(o);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setPopupOpen(false);
|
||||
}
|
||||
const user = await auth0Client!.getUser();
|
||||
setUser(user);
|
||||
setIsAuthenticated(true);
|
||||
};
|
||||
|
||||
const handleRedirectCallback = async () => {
|
||||
setLoading(true);
|
||||
const result = await auth0Client!.handleRedirectCallback();
|
||||
const user = await auth0Client!.getUser();
|
||||
setLoading(false);
|
||||
setIsAuthenticated(true);
|
||||
setUser(user);
|
||||
return result;
|
||||
};
|
||||
return (
|
||||
<Auth0Context.Provider
|
||||
value={{
|
||||
isAuthenticated,
|
||||
user,
|
||||
loading,
|
||||
popupOpen,
|
||||
loginWithPopup,
|
||||
handleRedirectCallback,
|
||||
getIdTokenClaims: (o: getIdTokenClaimsOptions | undefined) =>
|
||||
auth0Client!.getIdTokenClaims(o),
|
||||
loginWithRedirect: (o: RedirectLoginOptions) =>
|
||||
auth0Client!.loginWithRedirect(o),
|
||||
getTokenSilently: (o: GetTokenSilentlyOptions | undefined) =>
|
||||
auth0Client!.getTokenSilently(o),
|
||||
getTokenWithPopup: (o: GetTokenWithPopupOptions | undefined) =>
|
||||
auth0Client!.getTokenWithPopup(o),
|
||||
logout: (o: LogoutOptions | undefined) => auth0Client!.logout(o)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Auth0Context.Provider>
|
||||
);
|
||||
};
|
||||
|
|
@ -6,7 +6,13 @@ interface IProps {
|
|||
export const Avatar: FC<IProps> = ({ picture }) => {
|
||||
return (
|
||||
<>
|
||||
<img className="circle" src={picture} height="100vh" width="100vh" />
|
||||
<img
|
||||
className="circle"
|
||||
src={picture}
|
||||
height="100vh"
|
||||
width="100vh"
|
||||
alt="user avatar"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
16
client/src/components/Navbar.tsx
Normal file
16
client/src/components/Navbar.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react";
|
||||
import { useAuth0 } from "../authentication/auth0";
|
||||
|
||||
export const NavBar = () => {
|
||||
const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!isAuthenticated && (
|
||||
<button onClick={() => loginWithRedirect({})}>Log in</button>
|
||||
)}
|
||||
|
||||
{isAuthenticated && <button onClick={() => logout()}>Log out</button>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { FC } from "react";
|
||||
import { Project } from "../types/Project";
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
|
|
@ -7,6 +8,9 @@ interface IProps {
|
|||
setDescription: React.Dispatch<React.SetStateAction<string>>;
|
||||
endingDate: string;
|
||||
setEndingDate: React.Dispatch<React.SetStateAction<string>>;
|
||||
allProjects: Project[];
|
||||
projectId: string;
|
||||
setProjectId: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const NewTicketForm: FC<IProps> = ({
|
||||
|
|
@ -15,7 +19,10 @@ export const NewTicketForm: FC<IProps> = ({
|
|||
description,
|
||||
setDescription,
|
||||
endingDate,
|
||||
setEndingDate
|
||||
setEndingDate,
|
||||
allProjects,
|
||||
projectId,
|
||||
setProjectId
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -62,13 +69,23 @@ export const NewTicketForm: FC<IProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="input-field">
|
||||
<select id="project" className="browser-default">
|
||||
<option value="" disabled selected>
|
||||
<select
|
||||
id="project"
|
||||
className="browser-default"
|
||||
value={projectId}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
e.preventDefault();
|
||||
setProjectId(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value={0} disabled>
|
||||
Project
|
||||
</option>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="3">Option 3</option>
|
||||
{allProjects.map(p => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
import React, { FC, useState, ChangeEvent, useEffect, FormEvent } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import React, { FC, useState, FormEvent } from "react";
|
||||
import { useParams, useRouteMatch } from "react-router-dom";
|
||||
import { Modal } from "./Modal";
|
||||
import { NewTicketTabRouter } from "./NewTicketTabRouter";
|
||||
import { User } from "../types/User";
|
||||
import { NewTicketForm } from "./NewTicketForm";
|
||||
import { Ticket } from "../types/Ticket";
|
||||
import { patch, post } from "../utils/http";
|
||||
import { Constants } from "../utils/Constants";
|
||||
import { Project } from "../types/Project";
|
||||
import { post } from "../utils/http";
|
||||
import { Constants } from "../utils/Constants";
|
||||
import { HttpResponse } from "../types/HttpResponse";
|
||||
|
||||
interface IProps {
|
||||
show: boolean;
|
||||
handleClose(): void;
|
||||
allUsers: User[];
|
||||
allProjects: Project[];
|
||||
}
|
||||
|
||||
export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
|
||||
const [filterText, setFilterText] = useState<string>("");
|
||||
const { id } = useParams();
|
||||
export const NewTicketModal: FC<IProps> = ({
|
||||
show,
|
||||
handleClose,
|
||||
allProjects
|
||||
}) => {
|
||||
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 { url } = useRouteMatch();
|
||||
const id = url.split("/")[2];
|
||||
const [projectId, setProjectId] = useState(id);
|
||||
|
||||
const handleSubmit: (event: FormEvent<HTMLFormElement>) => void = async (
|
||||
e: FormEvent
|
||||
|
|
@ -35,20 +34,19 @@ export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
|
|||
let newTicket = {
|
||||
title: title,
|
||||
description: description,
|
||||
endingDate: endingDate,
|
||||
endingDate: new Date(endingDate).toISOString(),
|
||||
creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94",
|
||||
projectId: 1
|
||||
projectId: parseInt(projectId)
|
||||
};
|
||||
console.log(newTicket);
|
||||
// console.log(newTicket);
|
||||
const response: HttpResponse<Ticket> = await post<Ticket>(
|
||||
`${Constants.ticketsURI}`,
|
||||
newTicket
|
||||
);
|
||||
console.log(response.parsedBody);
|
||||
// console.log(response.parsedBody);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
useEffect(() => {});
|
||||
return (
|
||||
<Modal show={show} handleClose={handleClose}>
|
||||
<div className="row valign-wrapper indigo">
|
||||
|
|
@ -68,15 +66,16 @@ export const NewTicketModal: FC<IProps> = ({ show, handleClose, allUsers }) => {
|
|||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<NewTicketTabRouter
|
||||
tabNames={["Details", "Members"]}
|
||||
users={allUsers}
|
||||
<NewTicketForm
|
||||
title={title}
|
||||
setTitle={setTitle}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
endingDate={endingDate}
|
||||
setEndingDate={setEndingDate}
|
||||
allProjects={allProjects}
|
||||
projectId={projectId}
|
||||
setProjectId={setProjectId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
import React, { FC } from "react";
|
||||
import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
||||
import { useRouteMatch } 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;
|
||||
|
|
@ -19,7 +14,6 @@ interface IProps {
|
|||
|
||||
export const NewTicketTabRouter: FC<IProps> = ({
|
||||
tabNames,
|
||||
users,
|
||||
description,
|
||||
setDescription,
|
||||
title,
|
||||
|
|
@ -33,22 +27,14 @@ export const NewTicketTabRouter: FC<IProps> = ({
|
|||
<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>
|
||||
{/* <NewTicketForm
|
||||
title={title}
|
||||
setTitle={setTitle}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
endingDate={endingDate}
|
||||
setEndingDate={setEndingDate}
|
||||
/> */}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,18 @@ export const ProgressBar: FC<ProgressBarProps> = ({
|
|||
remainingDays
|
||||
}) => {
|
||||
const styleString: CSSProperties = { width: `${value}%` };
|
||||
const barColor: string = value < 75 ? "red" : "";
|
||||
let barColor: string = "green";
|
||||
|
||||
if (value < 100) {
|
||||
barColor = "yellow";
|
||||
}
|
||||
if (value < 200 / 3) {
|
||||
barColor = "orange";
|
||||
}
|
||||
if (value < 100 / 3) {
|
||||
barColor = "red";
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
|||
import { TabRouterHeader } from "./TabRouterHeader";
|
||||
import { TicketList } from "./TicketList";
|
||||
import { FileList } from "./AppFileList";
|
||||
import { ActivityList } from "./ActivityList";
|
||||
// import { ActivityList } from "./ActivityList";
|
||||
import { Ticket } from "../types/Ticket";
|
||||
import { AppFile } from "../types/AppFile";
|
||||
import { Activity } from "../types/Activity";
|
||||
import { User } from "../types/User";
|
||||
import { Project } from "../types/Project";
|
||||
|
||||
interface IProps {
|
||||
tickets: Ticket[];
|
||||
|
|
@ -15,7 +15,7 @@ interface IProps {
|
|||
tabNames: string[];
|
||||
files: AppFile[];
|
||||
activities: Activity[];
|
||||
allUsers: User[];
|
||||
allProjects: Project[];
|
||||
}
|
||||
|
||||
export const TabRouter: FC<IProps> = ({
|
||||
|
|
@ -23,7 +23,7 @@ export const TabRouter: FC<IProps> = ({
|
|||
tabNames,
|
||||
files,
|
||||
activities,
|
||||
allUsers
|
||||
allProjects
|
||||
}) => {
|
||||
const { url } = useRouteMatch();
|
||||
|
||||
|
|
@ -35,16 +35,16 @@ export const TabRouter: FC<IProps> = ({
|
|||
<Redirect from={url} to={`${url}/tickets`} />
|
||||
|
||||
<Route path={`${url}/tickets`}>
|
||||
<TicketList tickets={tickets} users={allUsers} />
|
||||
<TicketList tickets={tickets} allProjects={allProjects} />
|
||||
</Route>
|
||||
|
||||
<Route path={`${url}/files`}>
|
||||
<FileList files={files} />
|
||||
</Route>
|
||||
|
||||
<Route path={`${url}/activity`}>
|
||||
{/* <Route path={`${url}/activity`}>
|
||||
<ActivityList activities={activities} />
|
||||
</Route>
|
||||
</Route> */}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,23 +3,22 @@ import { Ticket } from "../types/Ticket";
|
|||
import { FloatingButton } from "./FloatingButton";
|
||||
import { HorizontalCard } from "./HorizontalCard";
|
||||
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";
|
||||
import { Project } from "../types/Project";
|
||||
|
||||
type TicketListProps = {
|
||||
tickets: Ticket[];
|
||||
users: User[];
|
||||
allProjects: Project[];
|
||||
};
|
||||
|
||||
export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
|
||||
export const TicketList: FC<TicketListProps> = ({ tickets, allProjects }) => {
|
||||
const [filterText, setFilterText] = useState<string>("");
|
||||
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||
setFilterText("");
|
||||
};
|
||||
// const archiveTicket = () => {};
|
||||
|
||||
const onClick: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -45,7 +44,7 @@ export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
|
|||
setShowNew(false);
|
||||
}}
|
||||
show={showNew}
|
||||
allUsers={users}
|
||||
allProjects={allProjects}
|
||||
/>
|
||||
<h3>Tickets</h3>
|
||||
<FloatingButton
|
||||
|
|
@ -59,7 +58,7 @@ export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
|
|||
clearFilterText={clearFilterText}
|
||||
/>
|
||||
</div>
|
||||
<div className="col s12 grey">
|
||||
<div className="col s12 grey lighten-1">
|
||||
<ul>
|
||||
{filteredTickets.length === 0 ? (
|
||||
<HorizontalCard />
|
||||
|
|
@ -76,7 +75,6 @@ export const TicketList: FC<TicketListProps> = ({ tickets, users }) => {
|
|||
{}
|
||||
);
|
||||
}}
|
||||
// archiveTicket={archiveTicket}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { TabRouterHeader } from "./TabRouterHeader";
|
|||
import { ProjectList } from "./ProjectList";
|
||||
import { Ticket } from "../types/Ticket";
|
||||
import { Project } from "../types/Project";
|
||||
import { TicketList } from "./TicketList";
|
||||
|
||||
interface IProps {
|
||||
tabNames: string[];
|
||||
|
|
@ -25,9 +26,9 @@ export const UserTabRouter: FC<IProps> = ({ tickets, tabNames, projects }) => {
|
|||
<ProjectList projects={projects} />
|
||||
</Route>
|
||||
|
||||
{/* <Route path={`${url}/tickets`}>
|
||||
<TicketList tickets={tickets} />
|
||||
</Route> */}
|
||||
<Route path={`${url}/tickets`}>
|
||||
<TicketList tickets={tickets} allProjects={[]} />
|
||||
</Route>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, useState, ChangeEvent, FormEvent } from "react";
|
||||
import React, { FC, useState, ChangeEvent, FormEvent, useEffect } from "react";
|
||||
import { Modal } from "./Modal";
|
||||
import { AvatarList } from "./AvatarList";
|
||||
import { User } from "../types/User";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
|
|||
return Boolean(members.find(m => m.id === id));
|
||||
};
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="valign-wrapper">
|
||||
<label htmlFor={user.id}>
|
||||
<input
|
||||
id={user.id}
|
||||
|
|
@ -27,15 +27,16 @@ export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
|
|||
/>
|
||||
<span>
|
||||
{user.fullName}
|
||||
<img
|
||||
className="circle"
|
||||
src={user.picture}
|
||||
width="32vh"
|
||||
height="32vh"
|
||||
alt={user.fullName}
|
||||
/>
|
||||
{" "}
|
||||
</span>
|
||||
</label>
|
||||
<img
|
||||
className="circle"
|
||||
src={user.picture}
|
||||
width="32vh"
|
||||
height="32vh"
|
||||
alt={user.fullName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { User } from "../types/User";
|
|||
export const ProjectController: FC = () => {
|
||||
const [project, setProject] = useState<Project>({} as Project);
|
||||
const [allUsers, setAllUsers] = useState<User[]>([]);
|
||||
const [allProjects, setAllProjects] = useState<Project[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
|
@ -48,10 +49,25 @@ export const ProjectController: FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function httpGetAllProjects(): Promise<void> {
|
||||
try {
|
||||
const response: HttpResponse<Project> = await get<Project>(
|
||||
`${Constants.projectsURI}`
|
||||
);
|
||||
if (response.parsedBody !== undefined) {
|
||||
setAllProjects((response.parsedBody as unknown) as Project[]);
|
||||
}
|
||||
} catch (ex) {
|
||||
setHasError(true);
|
||||
setError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== undefined) {
|
||||
httpGetProjects(id);
|
||||
httpGetAllUsers();
|
||||
httpGetAllProjects();
|
||||
} else {
|
||||
setHasError(true);
|
||||
setError("Bad Request");
|
||||
|
|
@ -61,6 +77,6 @@ export const ProjectController: FC = () => {
|
|||
if (hasError) {
|
||||
return <ErrorController error={error} />;
|
||||
}
|
||||
const viewModel = new ProjectVM(project, allUsers);
|
||||
const viewModel = new ProjectVM(project, allUsers, allProjects);
|
||||
return isLoading ? <Preloader /> : <ProjectPage viewModel={viewModel} />;
|
||||
};
|
||||
|
|
|
|||
29
client/src/index.jsx
Normal file
29
client/src/index.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { Auth0Provider } from "./authentication/auth0";
|
||||
import config from "./authentication/config.json";
|
||||
import history from "./utils/history";
|
||||
|
||||
const onRedirectCallback = appState => {
|
||||
history.push(
|
||||
appState && appState.targetUrl
|
||||
? appState.targetUrl
|
||||
: window.location.pathname
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Auth0Provider
|
||||
domain={config.domain}
|
||||
client_id={config.clientId}
|
||||
redirect_uri={window.location.origin}
|
||||
onRedirectCallback={onRedirectCallback}
|
||||
>
|
||||
<App />
|
||||
</Auth0Provider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
serviceWorker.unregister();
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
|
||||
serviceWorker.unregister();
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
import React, { FC } from "react";
|
||||
import { AppRouter } from "../utils/router";
|
||||
import { NavBar } from "../components/Navbar";
|
||||
|
||||
const Layout: FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* <NavBar />
|
||||
<BreadCrumb /> */}
|
||||
<header>
|
||||
<NavBar />
|
||||
</header>
|
||||
{/* <BreadCrumb /> */}
|
||||
<AppRouter />
|
||||
{/* <Footer /> */}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ interface IProps {
|
|||
|
||||
export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
||||
const {
|
||||
// id,
|
||||
title,
|
||||
description,
|
||||
users,
|
||||
|
|
@ -23,10 +24,11 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
|||
ticketsTotalCount,
|
||||
remainingDays,
|
||||
files,
|
||||
activities
|
||||
activities,
|
||||
allProjects
|
||||
} = viewModel;
|
||||
|
||||
const tabNames: string[] = ["Tickets", "Files", "Activity"];
|
||||
const tabNames: string[] = ["Tickets", "Files"]; //, "Activity"];
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
|
|
@ -59,7 +61,7 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
|||
tickets={tickets}
|
||||
files={files}
|
||||
activities={activities}
|
||||
allUsers={allUsers}
|
||||
allProjects={allProjects}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ export const UserPage: FC<IProps> = ({ viewModel }) => {
|
|||
tickets={tickets}
|
||||
/>
|
||||
</div>
|
||||
{/* // <TabView>
|
||||
// <CardList>
|
||||
// <CardList>
|
||||
// </TabView> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
2
client/src/utils/history.ts
Normal file
2
client/src/utils/history.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import { createBrowserHistory } from "history";
|
||||
export default createBrowserHistory;
|
||||
|
|
@ -58,5 +58,5 @@ const headers: Headers = new Headers({
|
|||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization:
|
||||
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4Mjk3MTQyMSwiZXhwIjoxNTgzMDU3ODIxLCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.mH_ejE0gpMXrMIVUV7afuagTopCxm2x7F7Ash9L7zfOCQnw71E7NPsEh8w2_nFFz3lwm988vGbJhB_1G0oetK3VnqgahHn-ZJfk8RhQeKZQtCddbFCXSZzbsvi8XekpN2qLSZswrfxM4hiNfedQW1sM6wSbVbv4q6MrpPrtnepOo5lu67b9eHQZA5MQGqCLqqAZtEAa4Z8bVUCUcf3wU4e9W38LngrMSEMN62_ZZ8AVnjFVQ97zWEadJhYT54S9tVioY8jNR-38qjuYH_ZP3mVQg8INza9YFiYzIsIgdYufhorb_cSXc1qK1ZhHf4kRHaiHCYan-c9nN9SM9MCYA9A"
|
||||
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UWkNSRFEzUkRnd1FUQXlNRFExTmtOQ09UQXlSamhGTURaRU1Ea3pNRGxHUkRrelFqZENSZyJ9.eyJpc3MiOiJodHRwczovL2Rldi1meWpydm9oeC5hdXRoMC5jb20vIiwic3ViIjoiR3dlZTlGUnN3ejNWNE5vZFVRTjJIcjJyQjJTMDI1UmZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvYXBpL1YxLyIsImlhdCI6MTU4MzI0ODU1NSwiZXhwIjoxNTgzMzM0OTU1LCJhenAiOiJHd2VlOUZSc3d6M1Y0Tm9kVVFOMkhyMnJCMlMwMjVSZiIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.lfgZAwqq3VHNAM1Y33Vj7Jtal6IdN92qWJHwu5T8l-56kBXNC2g1pm6TshRhYDKmedoJmSddjKXClA9eLu1Ve1X8wLj7CbrhZtGNsOcIEStF9icahCy8y2OdP2U6UXJJt9HehwqvwT3JltH_MqyYeJsyMsah3a3rlu6LoEAHNqF4jk8RUxZKjlVJz_iW-tbJ_GoGYrTp_bgvw6IBpgZvJDPGveaA6ms20CoN4zpXaL2ucgtaRbasXfD-z4NDwYN5_9TRPNcf9cuxViZ28CJ66uWxK8BEKysWnABYkh239Q71K2t41hFvQV8ti5l1UKcuqFf_lUGo0wYo9F-MGK6RKA"
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
|
|
@ -19,7 +15,5 @@
|
|||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue