mirror of
https://github.com/rjNemo/ticket_manager
synced 2026-06-06 00:36:39 +00:00
pulled react
This commit is contained in:
commit
f5edc62f00
75 changed files with 2721 additions and 943 deletions
|
|
@ -13,7 +13,7 @@ using TicketManager.Resources;
|
||||||
|
|
||||||
namespace TicketManager.Controllers
|
namespace TicketManager.Controllers
|
||||||
{
|
{
|
||||||
[Authorize]
|
// [Authorize]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("api/v1/users")]
|
[Route("api/v1/users")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using System;
|
||||||
namespace TicketManager.Controllers
|
namespace TicketManager.Controllers
|
||||||
{
|
{
|
||||||
// [Authorize(Roles = "Admin")]
|
// [Authorize(Roles = "Admin")]
|
||||||
[Authorize]
|
// [Authorize]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("api/v1/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using TicketManager.Models;
|
||||||
|
|
||||||
namespace TicketManager.Controllers
|
namespace TicketManager.Controllers
|
||||||
{
|
{
|
||||||
[Authorize]
|
// [Authorize]
|
||||||
[Route("api/v1/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class TicketsController : ControllerBase
|
public class TicketsController : ControllerBase
|
||||||
|
|
|
||||||
|
|
@ -53,3 +53,10 @@
|
||||||
- [x] Form validators
|
- [x] Form validators
|
||||||
- [x] API deployed to Azure
|
- [x] API deployed to Azure
|
||||||
- [x] Front-end deployment: build first then use ssh …
|
- [x] Front-end deployment: build first then use ssh …
|
||||||
|
- [x] Azure
|
||||||
|
- [ ] Refactor TabPanels code
|
||||||
|
- [ ] Refactor Lists code
|
||||||
|
- [ ] Query project members in UserPage
|
||||||
|
- [ ] Query progression info in UserPage
|
||||||
|
- [x] Add info fields in New Ticket Form
|
||||||
|
- [ ] Filter users in Users Modal
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ namespace TicketManager
|
||||||
{
|
{
|
||||||
Name = "Ruidy Nemausat",
|
Name = "Ruidy Nemausat",
|
||||||
Email = "ruidy.nemausat@gmail.com",
|
Email = "ruidy.nemausat@gmail.com",
|
||||||
Url = new Uri("https://ruidywebsite.herokuapp.com/"),
|
Url = new Uri("https://ruidyportfolio.herokuapp.com/"),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Set the comments path for the Swagger JSON and UI.
|
// Set the comments path for the Swagger JSON and UI.
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@
|
||||||
</CustomCommands>
|
</CustomCommands>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.2" />
|
||||||
|
|
@ -39,7 +41,6 @@
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.1" />
|
||||||
<!-- <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.1" /> -->
|
|
||||||
<PackageReference Include="Microsoft.OpenApi" Version="1.1.4" />
|
<PackageReference Include="Microsoft.OpenApi" Version="1.1.4" />
|
||||||
<PackageReference Include="Moq" Version="4.13.0" />
|
<PackageReference Include="Moq" Version="4.13.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
|
||||||
|
|
|
||||||
BIN
app.db
Normal file
BIN
app.db
Normal file
Binary file not shown.
356
client/package-lock.json
generated
356
client/package-lock.json
generated
|
|
@ -1057,6 +1057,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
||||||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||||
},
|
},
|
||||||
|
"@emotion/hash": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
|
||||||
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||||
|
|
@ -1282,6 +1287,93 @@
|
||||||
"@types/yargs": "^13.0.0"
|
"@types/yargs": "^13.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@material-ui/core": {
|
||||||
|
"version": "4.9.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.8.tgz",
|
||||||
|
"integrity": "sha512-4cslpG6oLoPWUfwPkX+hvbak4hAGiOfgXOu/UIYeeMrtsTEebC0Mirjoby7zhS4ny86YI3rXEFW6EZDmlj5n5w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"@material-ui/styles": "^4.9.6",
|
||||||
|
"@material-ui/system": "^4.9.6",
|
||||||
|
"@material-ui/types": "^5.0.0",
|
||||||
|
"@material-ui/utils": "^4.9.6",
|
||||||
|
"@types/react-transition-group": "^4.2.0",
|
||||||
|
"clsx": "^1.0.2",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"popper.js": "^1.14.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^16.8.0",
|
||||||
|
"react-transition-group": "^4.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@material-ui/icons": {
|
||||||
|
"version": "4.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz",
|
||||||
|
"integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@material-ui/lab": {
|
||||||
|
"version": "4.0.0-alpha.47",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.47.tgz",
|
||||||
|
"integrity": "sha512-+WC3O0M/769D3nO9Rqupusc+lob7tQMe5/DnOjAhZ0bpXlJbhZb7N84WkEk4JgQLj6ydP8e9Jhqd1lG+mGj+xw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"@material-ui/utils": "^4.9.6",
|
||||||
|
"clsx": "^1.0.4",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@material-ui/styles": {
|
||||||
|
"version": "4.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.6.tgz",
|
||||||
|
"integrity": "sha512-ijgwStEkw1OZ6gCz18hkjycpr/3lKs1hYPi88O/AUn4vMuuGEGAIrqKVFq/lADmZUNF3DOFIk8LDkp7zmjPxtA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"@emotion/hash": "^0.8.0",
|
||||||
|
"@material-ui/types": "^5.0.0",
|
||||||
|
"@material-ui/utils": "^4.9.6",
|
||||||
|
"clsx": "^1.0.2",
|
||||||
|
"csstype": "^2.5.2",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"jss": "^10.0.3",
|
||||||
|
"jss-plugin-camel-case": "^10.0.3",
|
||||||
|
"jss-plugin-default-unit": "^10.0.3",
|
||||||
|
"jss-plugin-global": "^10.0.3",
|
||||||
|
"jss-plugin-nested": "^10.0.3",
|
||||||
|
"jss-plugin-props-sort": "^10.0.3",
|
||||||
|
"jss-plugin-rule-value-function": "^10.0.3",
|
||||||
|
"jss-plugin-vendor-prefixer": "^10.0.3",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@material-ui/system": {
|
||||||
|
"version": "4.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.6.tgz",
|
||||||
|
"integrity": "sha512-QtfoAePyqXoZ2HUVSwGb1Ro0kucMCvVjbI0CdYIR21t0Opgfm1Oer6ni9P5lfeXA39xSt0wCierw37j+YES48Q==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"@material-ui/utils": "^4.9.6",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@material-ui/types": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg=="
|
||||||
|
},
|
||||||
|
"@material-ui/utils": {
|
||||||
|
"version": "4.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.6.tgz",
|
||||||
|
"integrity": "sha512-gqlBn0JPPTUZeAktn1rgMcy9Iczrr74ecx31tyZLVGdBGGzsxzM6PP6zeS7FuoLS6vG4hoZP7hWnOoHtkR0Kvw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.4",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@mrmlnc/readdir-enhanced": {
|
"@mrmlnc/readdir-enhanced": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
||||||
|
|
@ -1622,6 +1714,22 @@
|
||||||
"@types/react-router": "*"
|
"@types/react-router": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-swipeable-views": {
|
||||||
|
"version": "0.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-swipeable-views/-/react-swipeable-views-0.13.0.tgz",
|
||||||
|
"integrity": "sha512-orrreCcXev6IUXDuHf07RDDCAoIZRMSr95eyWmYNRfjic7w/O+68iPu0NCysVls+UygRNvoqZMuXI72N/58E1w==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-transition-group": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/stack-utils": {
|
"@types/stack-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||||
|
|
@ -3168,6 +3276,11 @@
|
||||||
"shallow-clone": "^0.1.2"
|
"shallow-clone": "^0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"clsx": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA=="
|
||||||
|
},
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
|
|
@ -3665,6 +3778,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz",
|
||||||
"integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY="
|
"integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY="
|
||||||
},
|
},
|
||||||
|
"css-vendor": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.6.2",
|
||||||
|
"is-in-browser": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-what": {
|
"css-what": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz",
|
||||||
|
|
@ -4091,6 +4213,30 @@
|
||||||
"utila": "~0.4"
|
"utila": "~0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dom-helpers": {
|
||||||
|
"version": "5.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
|
||||||
|
"integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.8.7",
|
||||||
|
"csstype": "^2.6.7"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
|
||||||
|
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
|
||||||
|
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dom-serializer": {
|
"dom-serializer": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||||
|
|
@ -6072,6 +6218,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
|
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
|
||||||
},
|
},
|
||||||
|
"hyphenate-style-name": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
|
@ -6371,6 +6522,11 @@
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-in-browser": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
|
||||||
|
},
|
||||||
"is-number": {
|
"is-number": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||||
|
|
@ -7780,6 +7936,83 @@
|
||||||
"verror": "1.10.0"
|
"verror": "1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jss": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"csstype": "^2.6.5",
|
||||||
|
"is-in-browser": "^1.1.3",
|
||||||
|
"tiny-warning": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-camel-case": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"hyphenate-style-name": "^1.0.3",
|
||||||
|
"jss": "10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-default-unit": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"jss": "10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-global": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"jss": "10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-nested": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"jss": "10.1.1",
|
||||||
|
"tiny-warning": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-props-sort": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"jss": "10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-rule-value-function": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"jss": "10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jss-plugin-vendor-prefixer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.3.1",
|
||||||
|
"css-vendor": "^2.0.7",
|
||||||
|
"jss": "10.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"jsx-ast-utils": {
|
"jsx-ast-utils": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
|
||||||
|
|
@ -7789,6 +8022,11 @@
|
||||||
"object.assign": "^4.1.0"
|
"object.assign": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"keycode": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
|
||||||
|
},
|
||||||
"killable": {
|
"killable": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||||
|
|
@ -9204,6 +9442,11 @@
|
||||||
"ts-pnp": "^1.1.2"
|
"ts-pnp": "^1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"popper.js": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
||||||
|
},
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.25",
|
"version": "1.0.25",
|
||||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
|
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
|
||||||
|
|
@ -10620,6 +10863,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.5.tgz",
|
||||||
"integrity": "sha512-+DMR2k5c6BqMDSMF8hLH0vYKtKTeikiFW+fj0LClN+XZg4N9b8QUAdHC62CGWNLTi/gnuuemNcNcTFrCvK1f+A=="
|
"integrity": "sha512-+DMR2k5c6BqMDSMF8hLH0vYKtKTeikiFW+fj0LClN+XZg4N9b8QUAdHC62CGWNLTi/gnuuemNcNcTFrCvK1f+A=="
|
||||||
},
|
},
|
||||||
|
"react-event-listener": {
|
||||||
|
"version": "0.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz",
|
||||||
|
"integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.2.0",
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"warning": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.12.0",
|
"version": "16.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
||||||
|
|
@ -10731,6 +10984,96 @@
|
||||||
"workbox-webpack-plugin": "4.3.1"
|
"workbox-webpack-plugin": "4.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-swipeable-views": {
|
||||||
|
"version": "0.13.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.13.9.tgz",
|
||||||
|
"integrity": "sha512-WXC2FKYvZ9QdJ31v9LjEJEl1bA7E4AcaloTkbW0uU0dYf5uvv4aOpiyxubvOkVl1a5L2UAHmKSif4TmJ9usrSg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "7.0.0",
|
||||||
|
"prop-types": "^15.5.4",
|
||||||
|
"react-swipeable-views-core": "^0.13.7",
|
||||||
|
"react-swipeable-views-utils": "^0.13.9",
|
||||||
|
"warning": "^4.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-swipeable-views-core": {
|
||||||
|
"version": "0.13.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.13.7.tgz",
|
||||||
|
"integrity": "sha512-ekn9oDYfBt0oqJSGGwLEhKvn+QaqMGTy//9dURTLf+vp7W5j6GvmKryYdnwJCDITaPFI2hujXV4CH9krhvaE5w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "7.0.0",
|
||||||
|
"warning": "^4.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-swipeable-views-utils": {
|
||||||
|
"version": "0.13.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.13.9.tgz",
|
||||||
|
"integrity": "sha512-QLGxRKrbJCbWz94vkWLzb1Daaa2Y/TZKmsNKQ6WSNrS+chrlfZ3z9tqZ7YUJlW6pRWp3QZdLSY3UE3cN0TXXmw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "7.0.0",
|
||||||
|
"keycode": "^2.1.7",
|
||||||
|
"prop-types": "^15.6.0",
|
||||||
|
"react-event-listener": "^0.6.0",
|
||||||
|
"react-swipeable-views-core": "^0.13.7",
|
||||||
|
"shallow-equal": "^1.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-transition-group": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"dom-helpers": "^5.0.1",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"read-pkg": {
|
"read-pkg": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
|
||||||
|
|
@ -11506,6 +11849,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shallow-equal": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
|
|
@ -12888,6 +13236,14 @@
|
||||||
"makeerror": "1.0.x"
|
"makeerror": "1.0.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"warning": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"watchpack": {
|
"watchpack": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth0/auth0-spa-js": "^1.6.4",
|
"@auth0/auth0-spa-js": "^1.6.4",
|
||||||
|
"@material-ui/core": "^4.9.8",
|
||||||
|
"@material-ui/icons": "^4.9.1",
|
||||||
|
"@material-ui/lab": "^4.0.0-alpha.47",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.4.0",
|
"@testing-library/react": "^9.4.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
|
@ -13,12 +16,14 @@
|
||||||
"@types/react": "^16.9.19",
|
"@types/react": "^16.9.19",
|
||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/react-router-dom": "^5.1.3",
|
"@types/react-router-dom": "^5.1.3",
|
||||||
|
"@types/react-swipeable-views": "^0.13.0",
|
||||||
"@types/underscore": "^1.9.4",
|
"@types/underscore": "^1.9.4",
|
||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-scripts": "3.3.1",
|
"react-scripts": "3.3.1",
|
||||||
|
"react-swipeable-views": "^0.13.9",
|
||||||
"typescript": "^3.7.5",
|
"typescript": "^3.7.5",
|
||||||
"underscore": "^1.9.2"
|
"underscore": "^1.9.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
|
||||||
<link
|
<!-- <link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
|
||||||
/>
|
/>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> -->
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
import React, { FC } from "react";
|
import React from "react";
|
||||||
import Layout from "./pages/Layout";
|
import { Router } from "react-router-dom";
|
||||||
import { useAuth0 } from "./authentication/auth0";
|
import { useAuth0 } from "./authentication/auth0";
|
||||||
|
import * as createHistory from "history";
|
||||||
|
// import history from "./utils/history";
|
||||||
|
import MainLayout from "./layouts/MainLayout";
|
||||||
|
import { AppRouter } from "./utils/router";
|
||||||
|
|
||||||
const App: FC = () => {
|
export const history = createHistory.createBrowserHistory();
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
const { loading } = useAuth0();
|
const { loading } = useAuth0();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|
@ -10,10 +16,10 @@ const App: FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<Router history={history}>
|
||||||
<Layout />
|
<MainLayout>
|
||||||
</div>
|
<AppRouter />
|
||||||
|
</MainLayout>
|
||||||
|
</Router>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
client/src/authentication/auth0.jsx
Normal file
90
client/src/authentication/auth0.jsx
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
// src/react-auth0-spa.js
|
||||||
|
import React, { useState, useEffect, useContext } from "react";
|
||||||
|
import createAuth0Client from "@auth0/auth0-spa-js";
|
||||||
|
|
||||||
|
const DEFAULT_REDIRECT_CALLBACK = () =>
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname);
|
||||||
|
|
||||||
|
export const Auth0Context = React.createContext();
|
||||||
|
export const useAuth0 = () => useContext(Auth0Context);
|
||||||
|
export const Auth0Provider = ({
|
||||||
|
children,
|
||||||
|
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
|
||||||
|
...initOptions
|
||||||
|
}) => {
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState();
|
||||||
|
const [user, setUser] = useState();
|
||||||
|
const [auth0Client, setAuth0] = useState();
|
||||||
|
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=") &&
|
||||||
|
window.location.search.includes("state=")
|
||||||
|
) {
|
||||||
|
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 (params = {}) => {
|
||||||
|
setPopupOpen(true);
|
||||||
|
try {
|
||||||
|
await auth0Client.loginWithPopup(params);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setPopupOpen(false);
|
||||||
|
}
|
||||||
|
const user = await auth0Client.getUser();
|
||||||
|
setUser(user);
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRedirectCallback = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
await auth0Client.handleRedirectCallback();
|
||||||
|
const user = await auth0Client.getUser();
|
||||||
|
setLoading(false);
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
setUser(user);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Auth0Context.Provider
|
||||||
|
value={{
|
||||||
|
isAuthenticated,
|
||||||
|
user,
|
||||||
|
loading,
|
||||||
|
popupOpen,
|
||||||
|
loginWithPopup,
|
||||||
|
handleRedirectCallback,
|
||||||
|
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
|
||||||
|
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
|
||||||
|
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
|
||||||
|
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
|
||||||
|
logout: (...p) => auth0Client.logout(...p)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Auth0Context.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
|
||||||
import { AppFile } from "../types/AppFile";
|
|
||||||
import { FileCollection } from "./FileCollection";
|
|
||||||
import { InputFile } from "./InputFile";
|
|
||||||
import { FilterBar } from "./FilterBar";
|
|
||||||
|
|
||||||
type IProps = {
|
|
||||||
files: AppFile[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FileList: FC<IProps> = ({ files }) => {
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<div className="row valign-wrapper">
|
|
||||||
<h3>Files</h3>
|
|
||||||
<FilterBar
|
|
||||||
filterText={filterText}
|
|
||||||
handleChange={handleChange}
|
|
||||||
clearFilterText={clearFilterText}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<InputFile />
|
|
||||||
<FileCollection files={files} filterText={filterText} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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"
|
|
||||||
alt="user avatar"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import React, { FC } from "react";
|
|
||||||
import { User } from "../types/User";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
interface AvatarListProps {
|
|
||||||
users: User[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AvatarList: FC<AvatarListProps> = ({ users }) => {
|
|
||||||
return users === undefined ? (
|
|
||||||
<></>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{users.map((user: User, i: number) => (
|
|
||||||
<Link to={`/users/${user.id}`} key={i}>
|
|
||||||
<img
|
|
||||||
className="circle"
|
|
||||||
src={user.picture}
|
|
||||||
width="32vh"
|
|
||||||
height="32vh"
|
|
||||||
alt={user.fullName}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
36
client/src/components/Avatars/AvatarList.tsx
Normal file
36
client/src/components/Avatars/AvatarList.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
|
import AvatarGroup from "@material-ui/lab/AvatarGroup";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
import { makeStyles, Theme, createStyles } from "@material-ui/core";
|
||||||
|
|
||||||
|
interface AvatarListProps {
|
||||||
|
users: User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AvatarList: FC<AvatarListProps> = ({ users }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
return users === undefined ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<AvatarGroup max={5}>
|
||||||
|
{users.map((user: User, i: number) => (
|
||||||
|
<Link to={`/users/${user.id}`} key={i}>
|
||||||
|
<Avatar src={user.picture} alt={user.fullName} />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</AvatarGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
37
client/src/components/Avatars/UserAvatar.tsx
Normal file
37
client/src/components/Avatars/UserAvatar.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
picture: string;
|
||||||
|
alt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
display: "flex",
|
||||||
|
"& > *": {
|
||||||
|
margin: theme.spacing(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
width: theme.spacing(3),
|
||||||
|
height: theme.spacing(3)
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
width: theme.spacing(10),
|
||||||
|
height: theme.spacing(10)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UserAvatar: FC<IProps> = ({ picture, alt }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Avatar alt={alt} src={picture} className={classes.large} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
59
client/src/components/ButtonAppBar.tsx
Normal file
59
client/src/components/ButtonAppBar.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from "react";
|
||||||
|
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import AppBar from "@material-ui/core/AppBar";
|
||||||
|
import Toolbar from "@material-ui/core/Toolbar";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
|
import MenuIcon from "@material-ui/icons/Menu";
|
||||||
|
import { useAuth0 } from "../authentication/auth0";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
menuButton: {
|
||||||
|
marginRight: theme.spacing(2)
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
flexGrow: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function ButtonAppBar() {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<AppBar position="static">
|
||||||
|
<Toolbar>
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
className={classes.menuButton}
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h6" className={classes.title}>
|
||||||
|
<Button color="inherit" href="/">
|
||||||
|
BugBuster
|
||||||
|
</Button>
|
||||||
|
</Typography>
|
||||||
|
{!isAuthenticated ? (
|
||||||
|
<Button color="inherit" onClick={() => loginWithRedirect({})}>
|
||||||
|
Log in
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button color="inherit" onClick={() => logout()}>
|
||||||
|
Log out
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
client/src/components/Buttons/FloatingButton.tsx
Normal file
26
client/src/components/Buttons/FloatingButton.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React, { FC, MouseEvent } from "react";
|
||||||
|
import { Fab } from "@material-ui/core";
|
||||||
|
import AddIcon from "@material-ui/icons/Add";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
icon?: string;
|
||||||
|
color?: "inherit" | "primary" | "secondary" | "default" | undefined;
|
||||||
|
onClick?: (e: MouseEvent) => void;
|
||||||
|
size?: "small" | "medium" | "large" | undefined;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FloatingButton: FC<IProps> = ({
|
||||||
|
color,
|
||||||
|
icon,
|
||||||
|
size,
|
||||||
|
text,
|
||||||
|
onClick
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Fab color={color} aria-label={icon} size={size} onClick={onClick}>
|
||||||
|
<AddIcon />
|
||||||
|
{text}
|
||||||
|
</Fab>
|
||||||
|
);
|
||||||
|
};
|
||||||
48
client/src/components/Cards/HorizontalCard.tsx
Normal file
48
client/src/components/Cards/HorizontalCard.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, { FC, ReactNode } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Card from "@material-ui/core/Card";
|
||||||
|
import CardActions from "@material-ui/core/CardActions";
|
||||||
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { ProgressBar } from "../Progress/ProgressBar";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
title?: string;
|
||||||
|
link?: string;
|
||||||
|
progress?: number;
|
||||||
|
content: ReactNode;
|
||||||
|
actions?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
root: {
|
||||||
|
minWidth: 275,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const HorizontalCard: FC<IProps> = ({
|
||||||
|
title,
|
||||||
|
link = "#",
|
||||||
|
content,
|
||||||
|
actions,
|
||||||
|
progress = 0,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={classes.root}>
|
||||||
|
<ProgressBar value={progress} />
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h5" component="h2">
|
||||||
|
<Link to={link}>
|
||||||
|
<b>{title ?? "Nothing to do"}</b>
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
{content}
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>{actions}</CardActions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
63
client/src/components/Cards/ProjectCard.tsx
Normal file
63
client/src/components/Cards/ProjectCard.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { HorizontalCard } from "./HorizontalCard";
|
||||||
|
import { makeStyles, Theme, createStyles } from "@material-ui/core";
|
||||||
|
import { AvatarList } from "../Avatars/AvatarList";
|
||||||
|
import { ProgressInfo } from "../Progress/ProgressInfo";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
import { getRemainingdays } from "../../utils/methods";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
title?: string;
|
||||||
|
remainingDays?: string;
|
||||||
|
link?: string;
|
||||||
|
members?: User[];
|
||||||
|
progress?: number;
|
||||||
|
ticketsNumber?: number;
|
||||||
|
ticketsDone?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
progress: {
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const ProjectCard: FC<IProps> = ({
|
||||||
|
title,
|
||||||
|
remainingDays = "",
|
||||||
|
link = "#",
|
||||||
|
members,
|
||||||
|
progress = 0,
|
||||||
|
ticketsNumber,
|
||||||
|
ticketsDone,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const Content: FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{members && <AvatarList users={members} />}
|
||||||
|
<div className={classes.progress}>
|
||||||
|
<ProgressInfo
|
||||||
|
remainingDays={getRemainingdays(remainingDays)}
|
||||||
|
tasksDone={ticketsDone}
|
||||||
|
tasksTotalCount={ticketsNumber}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HorizontalCard
|
||||||
|
title={title}
|
||||||
|
link={link}
|
||||||
|
content={<Content />}
|
||||||
|
progress={progress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectCard;
|
||||||
65
client/src/components/Cards/TicketCard.tsx
Normal file
65
client/src/components/Cards/TicketCard.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React, { FC, MouseEvent } from "react";
|
||||||
|
import { Button, Typography, Grid } from "@material-ui/core";
|
||||||
|
import { HorizontalCard } from "./HorizontalCard";
|
||||||
|
import TicketChipsArray from "./TicketChipsArray";
|
||||||
|
import { Ticket } from "../../types/Ticket";
|
||||||
|
import { getRemainingdays } from "../../utils/methods";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
ticket?: Ticket;
|
||||||
|
validateTicket?: (event: MouseEvent) => void;
|
||||||
|
link?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TicketCard: FC<IProps> = ({
|
||||||
|
link = "#",
|
||||||
|
validateTicket,
|
||||||
|
ticket = {} as Ticket,
|
||||||
|
}) => {
|
||||||
|
const Content: FC = () => {
|
||||||
|
return (
|
||||||
|
<Grid container justify="space-between" alignItems="center">
|
||||||
|
<TicketChipsArray
|
||||||
|
status={ticket.status}
|
||||||
|
category={ticket.category}
|
||||||
|
impact={ticket.impact}
|
||||||
|
difficulty={ticket.difficulty}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" component="p">
|
||||||
|
<span>
|
||||||
|
Due in{" "}
|
||||||
|
{ticket?.endingDate ? (
|
||||||
|
getRemainingdays(ticket?.endingDate)
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
<del>Too much</del> 0
|
||||||
|
</span>
|
||||||
|
)}{" "}
|
||||||
|
days
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Action = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button size="small" onClick={validateTicket}>
|
||||||
|
Mark as done
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HorizontalCard
|
||||||
|
title={ticket?.title}
|
||||||
|
link={link}
|
||||||
|
content={<Content />}
|
||||||
|
actions={<Action />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TicketCard;
|
||||||
76
client/src/components/Cards/TicketChipsArray.tsx
Normal file
76
client/src/components/Cards/TicketChipsArray.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { Grid, Chip, makeStyles, Theme, createStyles } from "@material-ui/core";
|
||||||
|
import CategoryIcon from "@material-ui/icons/Category";
|
||||||
|
import PriorityHighIcon from "@material-ui/icons/PriorityHigh";
|
||||||
|
import SpeedIcon from "@material-ui/icons/Speed";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
listStyle: "none",
|
||||||
|
padding: theme.spacing(0.5),
|
||||||
|
margin: 0,
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
chip: {
|
||||||
|
margin: theme.spacing(0.5),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
status: string;
|
||||||
|
category: string;
|
||||||
|
impact: string;
|
||||||
|
difficulty: string;
|
||||||
|
}
|
||||||
|
const TicketChipsArray: FC<IProps> = ({
|
||||||
|
status,
|
||||||
|
category,
|
||||||
|
impact,
|
||||||
|
difficulty,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const chipData = [
|
||||||
|
// { label: "status", value: status },
|
||||||
|
{ label: "category", value: category },
|
||||||
|
{ label: "impact", value: impact },
|
||||||
|
{ label: "difficulty", value: difficulty },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
// <Paper component="ul" className={classes.root}>
|
||||||
|
<Grid component="ul" className={classes.root}>
|
||||||
|
{chipData.map((data, i: number) => {
|
||||||
|
let icon = <CategoryIcon />;
|
||||||
|
let color: "inherit" | "default" | "primary" | "secondary" | undefined;
|
||||||
|
|
||||||
|
if (data.label === "impact") {
|
||||||
|
color = "primary";
|
||||||
|
icon = <PriorityHighIcon />;
|
||||||
|
}
|
||||||
|
if (data.label === "difficulty") {
|
||||||
|
color = "secondary";
|
||||||
|
icon = <SpeedIcon />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={i}>
|
||||||
|
<Chip
|
||||||
|
icon={icon}
|
||||||
|
color={color}
|
||||||
|
label={data.value}
|
||||||
|
className={classes.chip}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TicketChipsArray;
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
import { createStyles, Theme, makeStyles } from "@material-ui/core/styles";
|
||||||
|
import List from "@material-ui/core/List";
|
||||||
|
import ListItem from "@material-ui/core/ListItem";
|
||||||
|
import ListItemText from "@material-ui/core/ListItemText";
|
||||||
|
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
|
||||||
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
|
import WorkIcon from "@material-ui/icons/Work";
|
||||||
import { AppFile } from "../types/AppFile";
|
import { AppFile } from "../types/AppFile";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
|
|
@ -6,24 +13,32 @@ type IProps = {
|
||||||
filterText: string;
|
filterText: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 360,
|
||||||
|
backgroundColor: theme.palette.background.paper
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const FileCollection: FC<IProps> = ({ files, filterText }) => {
|
export const FileCollection: FC<IProps> = ({ files, filterText }) => {
|
||||||
console.log();
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<>
|
<List className={classes.root}>
|
||||||
<ul className="collection">
|
{files.length === 0 ? (
|
||||||
{files.length === 0 ? (
|
<FileEntry />
|
||||||
<FileEntry />
|
) : (
|
||||||
) : (
|
files
|
||||||
files
|
.filter(
|
||||||
.filter(
|
f =>
|
||||||
f =>
|
f.name.toLowerCase().includes(filterText.toLowerCase()) ||
|
||||||
f.name.toLowerCase().includes(filterText.toLowerCase()) ||
|
f.format.toLowerCase().includes(filterText.toLowerCase())
|
||||||
f.format.toLowerCase().includes(filterText.toLowerCase())
|
)
|
||||||
)
|
.map((file: AppFile) => <FileEntry file={file} key={file.id} />)
|
||||||
.map((file: AppFile) => <FileEntry file={file} key={file.id} />)
|
)}
|
||||||
)}
|
</List>
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -33,16 +48,16 @@ type IFProps = {
|
||||||
|
|
||||||
export const FileEntry: FC<IFProps> = ({ file }) => {
|
export const FileEntry: FC<IFProps> = ({ file }) => {
|
||||||
return (
|
return (
|
||||||
<li className="collection-item avatar">
|
<ListItem>
|
||||||
{/* <img src={require("../images/user_1.jpg")} alt="" className="circle" /> */}
|
<ListItemAvatar>
|
||||||
<i className="material-icons circle indigo lighten-1">folder</i>
|
<Avatar>
|
||||||
<span className="title">{file ? file.name : "Add your first file"}</span>
|
<WorkIcon />
|
||||||
<p>
|
</Avatar>
|
||||||
{file ? file.size : 0}kb {file ? file.format : "pdf"}
|
</ListItemAvatar>
|
||||||
</p>
|
<ListItemText
|
||||||
<a href="#!" className="secondary-content">
|
primary={file ? file.name : "Add your first file"}
|
||||||
<i className="material-icons">more_vert</i>
|
secondary={`${file ? file.size : 0}kb ${file ? file.format : "pdf"}`}
|
||||||
</a>
|
/>
|
||||||
</li>
|
</ListItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import React, { FC, ChangeEvent, MouseEvent } from "react";
|
import React, { FC, ChangeEvent, MouseEvent } from "react";
|
||||||
import { useRouteMatch } from "react-router-dom";
|
import { useRouteMatch } from "react-router-dom";
|
||||||
|
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import { Grid } from "@material-ui/core";
|
||||||
|
// import { AccountCircle, FilterList, FilterListSharp } from "@material-ui/icons";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
filterText: string;
|
filterText: string;
|
||||||
|
|
@ -7,35 +11,47 @@ type IProps = {
|
||||||
clearFilterText: (e: MouseEvent<HTMLInputElement>) => void;
|
clearFilterText: (e: MouseEvent<HTMLInputElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
"& > *": {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
width: "25ch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const FilterBar: FC<IProps> = ({
|
export const FilterBar: FC<IProps> = ({
|
||||||
filterText,
|
filterText,
|
||||||
handleChange,
|
handleChange,
|
||||||
clearFilterText
|
// clearFilterText
|
||||||
}) => {
|
}) => {
|
||||||
const { url } = useRouteMatch();
|
const { url } = useRouteMatch();
|
||||||
const placeholder: string = url.split("/")[3] || "users";
|
const placeholder: string = url.split("/")[3] || "elements";
|
||||||
|
const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={classes.margin}>
|
||||||
<div className="nav-wrapper">
|
<Grid container spacing={1} alignItems="flex-end">
|
||||||
<div className="input-field">
|
<Grid item>
|
||||||
<input
|
<TextField
|
||||||
// className="validate"
|
id="input-with-icon-grid"
|
||||||
id="filter"
|
variant="outlined"
|
||||||
type="search"
|
label={`Filter ${placeholder}`}
|
||||||
required
|
size="small"
|
||||||
name="filter"
|
|
||||||
value={filterText}
|
value={filterText}
|
||||||
placeholder={`Filter ${placeholder}`}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
color="primary"
|
||||||
|
className={classes.filter}
|
||||||
/>
|
/>
|
||||||
<label className="label-icon" htmlFor="search">
|
</Grid>
|
||||||
<i className="material-icons">filter_list</i>
|
</Grid>
|
||||||
</label>
|
</div>
|
||||||
<i className="material-icons" onClick={clearFilterText}>
|
|
||||||
close
|
|
||||||
</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import React, { FC, MouseEvent } from "react";
|
|
||||||
import { Button } from "./Button";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
icon?: string;
|
|
||||||
size?: string;
|
|
||||||
color?: string;
|
|
||||||
onClick?: (e: MouseEvent) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FloatingButton: FC<IProps> = ({
|
|
||||||
icon = "add",
|
|
||||||
size = "small",
|
|
||||||
color = "red",
|
|
||||||
onClick
|
|
||||||
}) => {
|
|
||||||
const iconComponent = <i className="material-icons left">{icon}</i>;
|
|
||||||
return (
|
|
||||||
<Button color={color} size={size} shape="btn-floating" onClick={onClick}>
|
|
||||||
{iconComponent}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
61
client/src/components/Footer.tsx
Normal file
61
client/src/components/Footer.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Container from "@material-ui/core/Container";
|
||||||
|
import Link from "@material-ui/core/Link";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
brand: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyParams: IProps = {
|
||||||
|
brand: "BugBuster",
|
||||||
|
text: "Made with 🔥"
|
||||||
|
};
|
||||||
|
|
||||||
|
const Copyright: FC<IProps> = ({ brand, text }) => {
|
||||||
|
return (
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{"© "}
|
||||||
|
<Link color="inherit" href="/">
|
||||||
|
{brand}
|
||||||
|
</Link>{" "}
|
||||||
|
{new Date().getFullYear()}
|
||||||
|
{`. All Rights Reserved. ${text}`}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
footer: {
|
||||||
|
padding: theme.spacing(3, 2),
|
||||||
|
marginTop: "auto",
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.type === "light"
|
||||||
|
? theme.palette.grey[200]
|
||||||
|
: theme.palette.grey[800]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className={classes.footer}>
|
||||||
|
<Container maxWidth="sm">
|
||||||
|
<Typography variant="body1">
|
||||||
|
<Link
|
||||||
|
color="inherit"
|
||||||
|
href="https://github.com/rjNemo"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
Ruidy Nemausat
|
||||||
|
</Link>{" "}
|
||||||
|
</Typography>
|
||||||
|
<Copyright brand={copyParams.brand} text={copyParams.text} />
|
||||||
|
</Container>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
import { Typography, Box } from "@material-ui/core";
|
||||||
|
|
||||||
type HeaderProps = {
|
type HeaderProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -7,9 +8,13 @@ type HeaderProps = {
|
||||||
|
|
||||||
export const Header: FC<HeaderProps> = ({ title, description }) => {
|
export const Header: FC<HeaderProps> = ({ title, description }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<Box>
|
||||||
<h1>{title}</h1>
|
<Typography variant="h2" component="h2">
|
||||||
<p className="lead">{description}</p>
|
{title}
|
||||||
</>
|
</Typography>
|
||||||
|
<Typography variant="subtitle2" component="h3">
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import React, { FC, MouseEvent } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { getRemainingdays } from "../utils/methods";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
title?: string;
|
|
||||||
remainingDays?: string;
|
|
||||||
validateTicket?: (event: MouseEvent) => void;
|
|
||||||
link?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const HorizontalCard: FC<IProps> = ({
|
|
||||||
title,
|
|
||||||
remainingDays,
|
|
||||||
link = "#",
|
|
||||||
validateTicket
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<li>
|
|
||||||
<div className="card horizontal">
|
|
||||||
<div className="card-stacked">
|
|
||||||
<div className="card-content">
|
|
||||||
<div className="row">
|
|
||||||
<div className="card-title">
|
|
||||||
<h6>
|
|
||||||
<Link to={link}>
|
|
||||||
<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="#">
|
|
||||||
<i className="material-icons" onClick={validateTicket}>
|
|
||||||
check
|
|
||||||
</i>
|
|
||||||
</Link>
|
|
||||||
{/* <Link to="#">
|
|
||||||
<i className="material-icons" onClick={archiveTicket}>
|
|
||||||
archive
|
|
||||||
</i>
|
|
||||||
</Link> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,29 +1,62 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
import { CloudUpload } from "@material-ui/icons";
|
||||||
|
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
|
||||||
type IProps = {};
|
export const InputFile: FC = () => {
|
||||||
|
|
||||||
export const InputFile: FC<IProps> = () => {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form action="/upload">
|
<form action="/upload">
|
||||||
<div className="file-field input-field">
|
<div className="file-field input-field">
|
||||||
<div className="btn indigo lighten-1">
|
<UploadButton>
|
||||||
<i className="material-icons ">cloud_upload</i>
|
<CloudUpload />
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*"
|
accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*"
|
||||||
/>
|
/>
|
||||||
</div>
|
</UploadButton>
|
||||||
<div className="file-path-wrapper">
|
|
||||||
<input
|
|
||||||
className="file-path validate"
|
|
||||||
type="text"
|
|
||||||
placeholder="Upload one or more files"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
"& > *": {
|
||||||
|
margin: theme.spacing(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
display: "none"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const UploadButton: FC = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<input
|
||||||
|
accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*"
|
||||||
|
className={classes.input}
|
||||||
|
id="contained-button-file"
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<label htmlFor="contained-button-file">
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
component="span"
|
||||||
|
startIcon={<CloudUpload />}
|
||||||
|
>
|
||||||
|
Upload files
|
||||||
|
</Button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
||||||
import { ActivityCollection } from "./ActivityCollection";
|
import { ActivityCollection } from "../ActivityCollection";
|
||||||
import { Activity } from "../types/Activity";
|
import { Activity } from "../../types/Activity";
|
||||||
import { FilterBar } from "./FilterBar";
|
import { FilterBar } from "../FilterBar";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
activities: Activity[];
|
activities: Activity[];
|
||||||
|
|
@ -12,9 +12,7 @@ export const ActivityList: FC<IProps> = ({ activities }) => {
|
||||||
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||||
setFilterText("");
|
setFilterText("");
|
||||||
};
|
};
|
||||||
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
e: ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
setFilterText(e.target.value);
|
setFilterText(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
40
client/src/components/Lists/AppFileList.tsx
Normal file
40
client/src/components/Lists/AppFileList.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
||||||
|
import { AppFile } from "../../types/AppFile";
|
||||||
|
import { FileCollection } from "../FileCollection";
|
||||||
|
import { InputFile } from "../InputFile";
|
||||||
|
import { FilterBar } from "../FilterBar";
|
||||||
|
import { Grid, Typography } from "@material-ui/core";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
files: AppFile[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FileList: FC<IProps> = ({ files }) => {
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
const clearFilterText = (e: MouseEvent): void => {
|
||||||
|
setFilterText("");
|
||||||
|
};
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs>
|
||||||
|
<Typography variant="h4" component="h4">
|
||||||
|
Files
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<FilterBar
|
||||||
|
filterText={filterText}
|
||||||
|
handleChange={handleChange}
|
||||||
|
clearFilterText={clearFilterText}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<InputFile />
|
||||||
|
<FileCollection files={files} filterText={filterText} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
||||||
import { UsersModalEntry } from "./UsersModalEntry";
|
import { UsersModalEntry } from "../Modals/UsersModalEntry";
|
||||||
import { FilterBar } from "./FilterBar";
|
import { FilterBar } from "../FilterBar";
|
||||||
import { User } from "../types/User";
|
import { User } from "../../types/User";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
users: User[];
|
users: User[];
|
||||||
|
|
@ -10,12 +10,10 @@ interface IProps {
|
||||||
export const MemberList: FC<IProps> = ({ users }) => {
|
export const MemberList: FC<IProps> = ({ users }) => {
|
||||||
const [members, setMembers] = useState<User[]>([]);
|
const [members, setMembers] = useState<User[]>([]);
|
||||||
const [filterText, setFilterText] = useState<string>("");
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
const clearFilterText = (e: MouseEvent): void => {
|
||||||
setFilterText("");
|
setFilterText("");
|
||||||
};
|
};
|
||||||
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
e: ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
setFilterText(e.target.value);
|
setFilterText(e.target.value);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
111
client/src/components/Lists/ProjectList.tsx
Normal file
111
client/src/components/Lists/ProjectList.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
||||||
|
import {
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
makeStyles,
|
||||||
|
createStyles,
|
||||||
|
Theme,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { FilterBar } from "../FilterBar";
|
||||||
|
import ProjectCard from "../Cards/ProjectCard";
|
||||||
|
import { FloatingButton } from "../Buttons/FloatingButton";
|
||||||
|
import { NewProjectModal } from "../Modals/NewProjectModal";
|
||||||
|
import { Project } from "../../types/Project";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
header: {
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
position: "relative",
|
||||||
|
marginLeft: "20px",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
projects: Project[];
|
||||||
|
allUsers: User[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProjectList: FC<IProps> = ({ projects, allUsers }) => {
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||||
|
setFilterText("");
|
||||||
|
};
|
||||||
|
const [showNew, setShowNew] = useState(false);
|
||||||
|
const onClick = (e: MouseEvent): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowNew(true);
|
||||||
|
};
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
let filteredProjects = projects.filter(
|
||||||
|
(t) =>
|
||||||
|
t.status !== "Done" &&
|
||||||
|
t.title.toLowerCase().includes(filterText.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NewProjectModal
|
||||||
|
handleClose={() => {
|
||||||
|
setShowNew(false);
|
||||||
|
}}
|
||||||
|
show={showNew}
|
||||||
|
allUsers={allUsers}
|
||||||
|
/>
|
||||||
|
<Grid container>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justify="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
126
client/src/components/Lists/TicketList.tsx
Normal file
126
client/src/components/Lists/TicketList.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
makeStyles,
|
||||||
|
Theme,
|
||||||
|
createStyles,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { FloatingButton } from "../Buttons/FloatingButton";
|
||||||
|
import { FilterBar } from "../FilterBar";
|
||||||
|
import { HttpResponse } from "../../types/HttpResponse";
|
||||||
|
import { Ticket } from "../../types/Ticket";
|
||||||
|
import { NewTicketModal } from "../Modals/NewTicketModal";
|
||||||
|
import { Project } from "../../types/Project";
|
||||||
|
import { put } from "../../utils/http";
|
||||||
|
import { Constants } from "../../utils/Constants";
|
||||||
|
import TicketCard from "../Cards/TicketCard";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
header: {
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
position: "relative",
|
||||||
|
marginLeft: "20px",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
type TicketListProps = {
|
||||||
|
tickets: Ticket[];
|
||||||
|
allProjects: Project[];
|
||||||
|
addButton?: Boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TicketList: FC<TicketListProps> = ({
|
||||||
|
tickets,
|
||||||
|
allProjects,
|
||||||
|
addButton = true,
|
||||||
|
}) => {
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
const clearFilterText = (e: MouseEvent): void => {
|
||||||
|
setFilterText("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = (e: MouseEvent): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowNew(true);
|
||||||
|
};
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [showNew, setShowNew] = useState(false);
|
||||||
|
let filteredTickets = tickets.filter(
|
||||||
|
(t) =>
|
||||||
|
t.status !== "Done" &&
|
||||||
|
t.title.toLowerCase().includes(filterText.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NewTicketModal
|
||||||
|
handleClose={() => {
|
||||||
|
setShowNew(false);
|
||||||
|
}}
|
||||||
|
show={showNew}
|
||||||
|
allProjects={allProjects}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Grid container>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justify="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
className={classes.header}
|
||||||
|
>
|
||||||
|
<Typography variant="h4" component="h4">
|
||||||
|
Tickets
|
||||||
|
{addButton && (
|
||||||
|
<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">
|
||||||
|
{filteredTickets.length === 0 ? (
|
||||||
|
<TicketCard />
|
||||||
|
) : (
|
||||||
|
filteredTickets.map((t: Ticket) => (
|
||||||
|
<TicketCard
|
||||||
|
key={t.id}
|
||||||
|
ticket={t}
|
||||||
|
link={`/tickets/${t.id}`}
|
||||||
|
validateTicket={async (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await put<HttpResponse<Ticket>>(
|
||||||
|
`${Constants.ticketsURI}/${t.id}/closed`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { InputField } from "./InputField";
|
import { InputField } from "./InputField";
|
||||||
import { PasswordField } from "./PasswordField";
|
import { PasswordField } from "./PasswordField";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Buttons/Button";
|
||||||
|
|
||||||
export const LogInForm: FC = () => {
|
export const LogInForm: FC = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import React, { FC, CSSProperties } from "react";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
handleClose: () => void;
|
|
||||||
show: boolean;
|
|
||||||
}
|
|
||||||
export const Modal: FC<IProps> = ({ handleClose, show, children }) => {
|
|
||||||
const showHideStyle: CSSProperties = show
|
|
||||||
? { display: "block", zIndex: 10 }
|
|
||||||
: { display: "none", zIndex: 10 };
|
|
||||||
return (
|
|
||||||
<div className="modal" style={showHideStyle}>
|
|
||||||
<div className="modal-content">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
79
client/src/components/Modals/Modal.tsx
Normal file
79
client/src/components/Modals/Modal.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
|
import {
|
||||||
|
DialogTitle,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
|
DialogContent,
|
||||||
|
makeStyles,
|
||||||
|
Theme,
|
||||||
|
createStyles,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import CloseIcon from "@material-ui/icons/Close";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
handleClose: () => void;
|
||||||
|
show: boolean;
|
||||||
|
action: string;
|
||||||
|
handleAction: (e: React.FormEvent) => Promise<void>;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
margin: 0,
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
},
|
||||||
|
closeButton: {
|
||||||
|
position: "absolute",
|
||||||
|
right: theme.spacing(1),
|
||||||
|
top: theme.spacing(1),
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Modal: FC<IProps> = ({
|
||||||
|
handleClose,
|
||||||
|
show,
|
||||||
|
action,
|
||||||
|
handleAction,
|
||||||
|
children,
|
||||||
|
name,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={show}
|
||||||
|
onClose={handleClose}
|
||||||
|
maxWidth="md"
|
||||||
|
// fullWidth
|
||||||
|
>
|
||||||
|
<DialogTitle disableTypography className={classes.root}>
|
||||||
|
<Typography variant="h6">{name}</Typography>
|
||||||
|
{handleClose ? (
|
||||||
|
<IconButton
|
||||||
|
aria-label="close"
|
||||||
|
className={classes.closeButton}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
) : null}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>{children}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleAction} color="primary">
|
||||||
|
{action}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
90
client/src/components/Modals/NewProjectModal.tsx
Normal file
90
client/src/components/Modals/NewProjectModal.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { FC, useState, FormEvent } from "react";
|
||||||
|
import { TextField } from "@material-ui/core";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
import { Project } from "../../types/Project";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
import { post } from "../../utils/http";
|
||||||
|
import { Constants } from "../../utils/Constants";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
show: boolean;
|
||||||
|
handleClose: () => void;
|
||||||
|
allUsers: User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NewProjectModal: FC<IProps> = ({ show, handleClose }) => {
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
|
const [endingDate, setEndingDate] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let newProject = {
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
endingDate: new Date(endingDate).toISOString(),
|
||||||
|
managerId: "cd179eb7-3a54-4060-b22c-3e947bdffcbc", // get current User id
|
||||||
|
};
|
||||||
|
|
||||||
|
await post<Project>(`${Constants.projectsURI}`, newProject);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
name="New Project"
|
||||||
|
show={show}
|
||||||
|
handleClose={handleClose}
|
||||||
|
action="New Project"
|
||||||
|
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="date"
|
||||||
|
name="date"
|
||||||
|
label="Due Date"
|
||||||
|
type="date"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
required
|
||||||
|
value={endingDate}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setEndingDate(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
213
client/src/components/Modals/NewTicketModal.tsx
Normal file
213
client/src/components/Modals/NewTicketModal.tsx
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
import React, { FC, useState, FormEvent } from "react";
|
||||||
|
import { useRouteMatch } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
TextField,
|
||||||
|
MenuItem,
|
||||||
|
Grid,
|
||||||
|
makeStyles,
|
||||||
|
Theme,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
import { Ticket } from "../../types/Ticket";
|
||||||
|
import { Project } from "../../types/Project";
|
||||||
|
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;
|
||||||
|
allProjects: Project[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
select: {
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const NewTicketModal: FC<IProps> = ({
|
||||||
|
show,
|
||||||
|
handleClose,
|
||||||
|
allProjects,
|
||||||
|
}) => {
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
{allProjects.map((p) => (
|
||||||
|
<MenuItem key={p.id} value={p.id}>
|
||||||
|
{p.title}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
114
client/src/components/Modals/UsersModal.tsx
Normal file
114
client/src/components/Modals/UsersModal.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import React, { FC, useState, ChangeEvent, FormEvent } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Grid } from "@material-ui/core";
|
||||||
|
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import List from "@material-ui/core/List";
|
||||||
|
import ListItem from "@material-ui/core/ListItem";
|
||||||
|
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
|
||||||
|
import ListItemText from "@material-ui/core/ListItemText";
|
||||||
|
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
|
||||||
|
import Checkbox from "@material-ui/core/Checkbox";
|
||||||
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
import { AvatarList } from "../Avatars/AvatarList";
|
||||||
|
import { FilterBar } from "../FilterBar";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
import { patch } from "../../utils/http";
|
||||||
|
import { Constants } from "../../utils/Constants";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
show: boolean;
|
||||||
|
users: User[];
|
||||||
|
allUsers: User[];
|
||||||
|
handleClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 360,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UsersModal: FC<IProps> = ({
|
||||||
|
show,
|
||||||
|
handleClose,
|
||||||
|
users,
|
||||||
|
allUsers,
|
||||||
|
}) => {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const memberIDs = users.map((u) => u.id);
|
||||||
|
const [members, setMembers] = useState<string[]>(memberIDs);
|
||||||
|
|
||||||
|
const handleToggle = (value: string) => () => {
|
||||||
|
const currentIndex = members.indexOf(value);
|
||||||
|
const newChecked = [...members];
|
||||||
|
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
newChecked.push(value);
|
||||||
|
} else {
|
||||||
|
newChecked.splice(currentIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMembers(newChecked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await patch<User[]>(
|
||||||
|
`${Constants.projectsURI}/${id}/members`,
|
||||||
|
members //.map((m) => m.id)
|
||||||
|
);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
name="Manage Users"
|
||||||
|
show={show}
|
||||||
|
handleClose={handleClose}
|
||||||
|
action="Submit"
|
||||||
|
handleAction={handleSubmit}
|
||||||
|
>
|
||||||
|
<Grid container justify="center">
|
||||||
|
<AvatarList users={users} />
|
||||||
|
<FilterBar
|
||||||
|
filterText={filterText}
|
||||||
|
clearFilterText={() => setFilterText("")}
|
||||||
|
handleChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid container justify="center">
|
||||||
|
<List dense className={classes.root}>
|
||||||
|
{allUsers.map((u: User) => (
|
||||||
|
<ListItem key={u.id}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar alt={u.fullName} src={u.picture} />
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText id={u.id} primary={u.fullName} />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Checkbox
|
||||||
|
edge="end"
|
||||||
|
onChange={handleToggle(u.id)}
|
||||||
|
checked={members.indexOf(u.id) !== -1}
|
||||||
|
inputProps={{ "aria-labelledby": `checkbox-${u.id}` }}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Grid>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { User } from "../types/User";
|
import { User } from "../../types/User";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
setMembers: React.Dispatch<React.SetStateAction<User[]>>;
|
setMembers: React.Dispatch<React.SetStateAction<User[]>>;
|
||||||
|
|
@ -9,7 +9,7 @@ interface IProps {
|
||||||
|
|
||||||
export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
|
export const UsersModalEntry: FC<IProps> = ({ user, setMembers, 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));
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="valign-wrapper">
|
<div className="valign-wrapper">
|
||||||
|
|
@ -22,7 +22,7 @@ export const UsersModalEntry: FC<IProps> = ({ user, setMembers, members }) => {
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
!match(user.id)
|
!match(user.id)
|
||||||
? setMembers([...members, user])
|
? setMembers([...members, user])
|
||||||
: setMembers(members.filter(p => p.id !== user.id));
|
: setMembers(members.filter((p) => p.id !== user.id));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
import { TextField, MenuItem } from "@material-ui/core";
|
||||||
import { Project } from "../types/Project";
|
import { Project } from "../types/Project";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
|
@ -22,45 +23,17 @@ export const NewTicketForm: FC<IProps> = ({
|
||||||
setEndingDate,
|
setEndingDate,
|
||||||
allProjects,
|
allProjects,
|
||||||
projectId,
|
projectId,
|
||||||
setProjectId
|
setProjectId,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row">
|
{/* <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">
|
<div className="input-field">
|
||||||
<i className="material-icons prefix">date_range</i>
|
<i className="material-icons prefix">date_range</i>
|
||||||
<input
|
<input
|
||||||
id="Due Date"
|
id="Due Date"
|
||||||
type="text"
|
type="text"
|
||||||
className="datepicker"
|
className="datepicker"
|
||||||
value={endingDate}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setEndingDate(e.target.value)
|
setEndingDate(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
@ -81,14 +54,14 @@ export const NewTicketForm: FC<IProps> = ({
|
||||||
<option value={0} disabled>
|
<option value={0} disabled>
|
||||||
Project
|
Project
|
||||||
</option>
|
</option>
|
||||||
{allProjects.map(p => (
|
{allProjects.map((p) => (
|
||||||
<option key={p.id} value={p.id}>
|
<option key={p.id} value={p.id}>
|
||||||
{p.title}
|
{p.title}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import React, { FC, useState, FormEvent } from "react";
|
|
||||||
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";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
show: boolean;
|
|
||||||
handleClose(): void;
|
|
||||||
allProjects: Project[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NewTicketModal: FC<IProps> = ({
|
|
||||||
show,
|
|
||||||
handleClose,
|
|
||||||
allProjects
|
|
||||||
}) => {
|
|
||||||
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 handleSubmit: (event: FormEvent<HTMLFormElement>) => void = async (
|
|
||||||
e: FormEvent
|
|
||||||
) => {
|
|
||||||
e.preventDefault();
|
|
||||||
let newTicket = {
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
endingDate: new Date(endingDate).toISOString(),
|
|
||||||
creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94",
|
|
||||||
projectId: parseInt(projectId)
|
|
||||||
};
|
|
||||||
|
|
||||||
// const response: HttpResponse<Ticket> =
|
|
||||||
await post<Ticket>(`${Constants.ticketsURI}`, newTicket);
|
|
||||||
handleClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
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">
|
|
||||||
<NewTicketForm
|
|
||||||
title={title}
|
|
||||||
setTitle={setTitle}
|
|
||||||
description={description}
|
|
||||||
setDescription={setDescription}
|
|
||||||
endingDate={endingDate}
|
|
||||||
setEndingDate={setEndingDate}
|
|
||||||
allProjects={allProjects}
|
|
||||||
projectId={projectId}
|
|
||||||
setProjectId={setProjectId}
|
|
||||||
/>
|
|
||||||
</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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import React, { FC } from "react";
|
|
||||||
import { useRouteMatch } from "react-router-dom";
|
|
||||||
import { TabRouterHeader } from "./TabRouterHeader";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
tabNames: string[];
|
|
||||||
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,
|
|
||||||
description,
|
|
||||||
setDescription,
|
|
||||||
title,
|
|
||||||
setTitle,
|
|
||||||
endingDate,
|
|
||||||
setEndingDate
|
|
||||||
}) => {
|
|
||||||
const { url } = useRouteMatch();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="row">
|
|
||||||
<TabRouterHeader tabNames={tabNames} />
|
|
||||||
|
|
||||||
{/* <NewTicketForm
|
|
||||||
title={title}
|
|
||||||
setTitle={setTitle}
|
|
||||||
description={description}
|
|
||||||
setDescription={setDescription}
|
|
||||||
endingDate={endingDate}
|
|
||||||
setEndingDate={setEndingDate}
|
|
||||||
/> */}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
118
client/src/components/Panels/ProjectTabPanel.tsx
Normal file
118
client/src/components/Panels/ProjectTabPanel.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React, { FC, useState, ReactNode } from "react";
|
||||||
|
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles";
|
||||||
|
import AppBar from "@material-ui/core/AppBar";
|
||||||
|
import Tabs from "@material-ui/core/Tabs";
|
||||||
|
import Tab from "@material-ui/core/Tab";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Box from "@material-ui/core/Box";
|
||||||
|
import SwipeableViews from "react-swipeable-views";
|
||||||
|
import { Ticket } from "../../types/Ticket";
|
||||||
|
import { Project } from "../../types/Project";
|
||||||
|
import { TicketList } from "../Lists/TicketList";
|
||||||
|
// import { FileList } from "./AppFileList";
|
||||||
|
import { AppFile } from "../../types/AppFile";
|
||||||
|
|
||||||
|
interface TabProps {
|
||||||
|
children?: ReactNode;
|
||||||
|
dir?: string;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabPanel: FC<TabProps> = (props: TabProps) => {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
component="div"
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`full-width-tabpanel-${index}`}
|
||||||
|
aria-labelledby={`full-width-tab-${index}`}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && <Box p={3}>{children}</Box>}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const a11yProps = (index: any) => {
|
||||||
|
return {
|
||||||
|
id: `full-width-tab-${index}`,
|
||||||
|
"aria-controls": `full-width-tabpanel-${index}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
backgroundColor: "#E9ECEF",
|
||||||
|
borderRadius: "20px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
tickets: Ticket[];
|
||||||
|
remainingDays?: number;
|
||||||
|
tabNames: string[];
|
||||||
|
files: AppFile[];
|
||||||
|
// activities: Activity[];
|
||||||
|
allProjects: Project[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectTabPanel: FC<IProps> = ({
|
||||||
|
tickets,
|
||||||
|
tabNames,
|
||||||
|
files,
|
||||||
|
allProjects,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
|
setValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeIndex = (index: number) => {
|
||||||
|
setValue(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<AppBar
|
||||||
|
position="static"
|
||||||
|
color="inherit"
|
||||||
|
style={{ borderTopLeftRadius: "10px", borderTopRightRadius: "10px" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
variant="fullWidth"
|
||||||
|
aria-label="full width tabs"
|
||||||
|
>
|
||||||
|
{tabNames.map((t: string, i: number) => (
|
||||||
|
<Tab key={i} label={t} {...a11yProps({ i })} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</AppBar>
|
||||||
|
<SwipeableViews
|
||||||
|
axis={theme.direction === "rtl" ? "x-reverse" : "x"}
|
||||||
|
index={value}
|
||||||
|
onChangeIndex={handleChangeIndex}
|
||||||
|
>
|
||||||
|
<TabPanel value={value} index={0} dir={theme.direction}>
|
||||||
|
<TicketList
|
||||||
|
tickets={tickets}
|
||||||
|
allProjects={allProjects}
|
||||||
|
addButton={true}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
{/* <TabPanel value={value} index={1} dir={theme.direction}>
|
||||||
|
<FileList files={files} />
|
||||||
|
</TabPanel> */}
|
||||||
|
</SwipeableViews>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
||||||
import { TabRouterHeader } from "./TabRouterHeader";
|
import { TabRouterHeader } from "./TabRouterHeader";
|
||||||
import { TicketList } from "./TicketList";
|
import { TicketList } from "../Lists/TicketList";
|
||||||
import { FileList } from "./AppFileList";
|
import { FileList } from "../Lists/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 { Activity } from "../../types/Activity";
|
||||||
import { Activity } from "../types/Activity";
|
import { Project } from "../../types/Project";
|
||||||
import { Project } from "../types/Project";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tickets: Ticket[];
|
tickets: Ticket[];
|
||||||
|
|
@ -23,7 +22,7 @@ export const TabRouter: FC<IProps> = ({
|
||||||
tabNames,
|
tabNames,
|
||||||
files,
|
files,
|
||||||
activities,
|
activities,
|
||||||
allProjects
|
allProjects,
|
||||||
}) => {
|
}) => {
|
||||||
const { url } = useRouteMatch();
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
109
client/src/components/Panels/UserTabPanel.tsx
Normal file
109
client/src/components/Panels/UserTabPanel.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import React, { FC, useState, ReactNode } from "react";
|
||||||
|
import SwipeableViews from "react-swipeable-views";
|
||||||
|
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles";
|
||||||
|
import AppBar from "@material-ui/core/AppBar";
|
||||||
|
import Tabs from "@material-ui/core/Tabs";
|
||||||
|
import Tab from "@material-ui/core/Tab";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Box from "@material-ui/core/Box";
|
||||||
|
import { Ticket } from "../../types/Ticket";
|
||||||
|
import { Project } from "../../types/Project";
|
||||||
|
import { ProjectList } from "../Lists/ProjectList";
|
||||||
|
import { TicketList } from "../Lists/TicketList";
|
||||||
|
import { User } from "../../types/User";
|
||||||
|
|
||||||
|
interface TabProps {
|
||||||
|
children?: ReactNode;
|
||||||
|
dir?: string;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabPanel: FC<TabProps> = (props: TabProps) => {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
component="div"
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`full-width-tabpanel-${index}`}
|
||||||
|
aria-labelledby={`full-width-tab-${index}`}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && <Box p={3}>{children}</Box>}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const a11yProps = (index: any) => {
|
||||||
|
return {
|
||||||
|
id: `full-width-tab-${index}`,
|
||||||
|
"aria-controls": `full-width-tabpanel-${index}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
backgroundColor: "#E9ECEF",
|
||||||
|
borderRadius: "20px",
|
||||||
|
},
|
||||||
|
topbar: { borderTopLeftRadius: "10px", borderTopRightRadius: "10px" },
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
tabNames: string[];
|
||||||
|
tickets: Ticket[];
|
||||||
|
projects: Project[];
|
||||||
|
allUsers: User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserTabPanel: FC<IProps> = ({
|
||||||
|
tickets,
|
||||||
|
tabNames,
|
||||||
|
projects,
|
||||||
|
allUsers,
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
|
setValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangeIndex = (index: number) => {
|
||||||
|
setValue(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<AppBar position="static" color="inherit" className={classes.topbar}>
|
||||||
|
<Tabs
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
variant="fullWidth"
|
||||||
|
aria-label="full width tabs"
|
||||||
|
>
|
||||||
|
{tabNames.map((t: string, i: number) => (
|
||||||
|
<Tab key={i} label={t} {...a11yProps({ i })} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</AppBar>
|
||||||
|
<SwipeableViews
|
||||||
|
axis={theme.direction === "rtl" ? "x-reverse" : "x"}
|
||||||
|
index={value}
|
||||||
|
onChangeIndex={handleChangeIndex}
|
||||||
|
>
|
||||||
|
<TabPanel value={value} index={0} dir={theme.direction}>
|
||||||
|
<ProjectList projects={projects} allUsers={allUsers} />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={value} index={1} dir={theme.direction}>
|
||||||
|
<TicketList tickets={tickets} allProjects={[]} addButton={false} />
|
||||||
|
</TabPanel>
|
||||||
|
</SwipeableViews>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
||||||
import { TabRouterHeader } from "./TabRouterHeader";
|
import { TabRouterHeader } from "./TabRouterHeader";
|
||||||
import { ProjectList } from "./ProjectList";
|
import { ProjectList } from "../Lists/ProjectList";
|
||||||
import { Ticket } from "../types/Ticket";
|
import { Ticket } from "../../types/Ticket";
|
||||||
import { Project } from "../types/Project";
|
import { Project } from "../../types/Project";
|
||||||
import { TicketList } from "./TicketList";
|
import { TicketList } from "../Lists/TicketList";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tabNames: string[];
|
tabNames: string[];
|
||||||
|
|
@ -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`}>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { HorizontalCard } from "./HorizontalCard";
|
import { UserAvatar } from "./Avatars/UserAvatar";
|
||||||
import { Avatar } from "./Avatar";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export const ProfileSelector: FC = () => {
|
export const ProfileSelector: FC = () => {
|
||||||
|
|
@ -10,7 +9,10 @@ export const ProfileSelector: FC = () => {
|
||||||
<div className="center ">
|
<div className="center ">
|
||||||
<h4>Select a profile</h4>
|
<h4>Select a profile</h4>
|
||||||
<Link to="/users/cd179eb7-3a54-4060-b22c-3e947bdffcbc">
|
<Link to="/users/cd179eb7-3a54-4060-b22c-3e947bdffcbc">
|
||||||
<Avatar picture="https://vignette.wikia.nocookie.net/jamescameronsavatar/images/0/08/Neytiri_Profilbild.jpg/revision/latest/scale-to-width-down/250?cb=20100107164021&path-prefix=de" />
|
<UserAvatar
|
||||||
|
alt=""
|
||||||
|
picture="https://vignette.wikia.nocookie.net/jamescameronsavatar/images/0/08/Neytiri_Profilbild.jpg/revision/latest/scale-to-width-down/250?cb=20100107164021&path-prefix=de"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<h5>Demo User</h5>
|
<h5>Demo User</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
45
client/src/components/Progress/ProgressBar.tsx
Normal file
45
client/src/components/Progress/ProgressBar.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { makeStyles, Theme, createStyles } from "@material-ui/core/styles";
|
||||||
|
import LinearProgress from "@material-ui/core/LinearProgress";
|
||||||
|
import { Box } from "@material-ui/core";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
width: "100%",
|
||||||
|
"& > * + *": {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ProgressBar: FC<IProps> = ({ value }) => {
|
||||||
|
// const styleString: CSSProperties = { width: `${value}%` };
|
||||||
|
// let barColor: string = "green";
|
||||||
|
|
||||||
|
// if (value < 100) {
|
||||||
|
// barColor = "yellow";
|
||||||
|
// }
|
||||||
|
// if (value < 200 / 3) {
|
||||||
|
// barColor = "orange";
|
||||||
|
// }
|
||||||
|
// if (value < 100 / 3) {
|
||||||
|
// barColor = "red";
|
||||||
|
// }
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className="row">
|
||||||
|
<div className={classes.root}>
|
||||||
|
<LinearProgress variant="determinate" value={value} />
|
||||||
|
{/* <LinearProgress variant="determinate" value={value} color={barColor} /> */}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
41
client/src/components/Progress/ProgressInfo.tsx
Normal file
41
client/src/components/Progress/ProgressInfo.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
// import { makeStyles, Theme, createStyles } from "@material-ui/core/styles";
|
||||||
|
import { Box } from "@material-ui/core";
|
||||||
|
import { PlaylistAddCheck } from "@material-ui/icons";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
tasksTotalCount?: number;
|
||||||
|
tasksDone?: number;
|
||||||
|
remainingDays?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
// createStyles({
|
||||||
|
// root: {
|
||||||
|
// width: "100%",
|
||||||
|
// "& > * + *": {
|
||||||
|
// marginTop: theme.spacing(2),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
|
export const ProgressInfo: FC<IProps> = ({
|
||||||
|
tasksDone,
|
||||||
|
tasksTotalCount,
|
||||||
|
remainingDays,
|
||||||
|
}) => {
|
||||||
|
// const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<PlaylistAddCheck />
|
||||||
|
<span>
|
||||||
|
{tasksDone}/{tasksTotalCount}
|
||||||
|
</span>
|
||||||
|
<Box className="right">
|
||||||
|
<span>Due in {remainingDays} days</span>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import React, { FC, CSSProperties } from "react";
|
|
||||||
|
|
||||||
type ProgressBarProps = {
|
|
||||||
value: number;
|
|
||||||
max?: number;
|
|
||||||
tasksTotalCount?: number;
|
|
||||||
tasksDone?: number;
|
|
||||||
remainingDays?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProgressBar: FC<ProgressBarProps> = ({
|
|
||||||
value,
|
|
||||||
max = 100,
|
|
||||||
tasksDone,
|
|
||||||
tasksTotalCount,
|
|
||||||
remainingDays
|
|
||||||
}) => {
|
|
||||||
const styleString: CSSProperties = { width: `${value}%` };
|
|
||||||
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">
|
|
||||||
<div className="progress">
|
|
||||||
<div className={`determinate ${barColor}`} style={styleString}></div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<i className="left material-icons">playlist_add_check</i>
|
|
||||||
<span>
|
|
||||||
{tasksDone}/{tasksTotalCount}
|
|
||||||
</span>
|
|
||||||
<div className="right">
|
|
||||||
<span>Due {remainingDays} days</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
|
||||||
import { Ticket } from "../types/Ticket";
|
|
||||||
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 handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
|
||||||
e: ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
setFilterText(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
let filteredTickets = projects.filter(
|
|
||||||
t =>
|
|
||||||
t.status !== "Done" &&
|
|
||||||
t.title.toLowerCase().includes(filterText.toLowerCase())
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="row valign-wrapper">
|
|
||||||
<h3>Projects</h3>
|
|
||||||
<FilterBar
|
|
||||||
filterText={filterText}
|
|
||||||
handleChange={handleChange}
|
|
||||||
clearFilterText={clearFilterText}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col s12 grey lighten-1">
|
|
||||||
<ul>
|
|
||||||
{filteredTickets.length === 0 ? (
|
|
||||||
<HorizontalCard />
|
|
||||||
) : (
|
|
||||||
filteredTickets.map((t: Project) => (
|
|
||||||
<HorizontalCard
|
|
||||||
key={t.id}
|
|
||||||
title={t.title}
|
|
||||||
remainingDays={t.endingDate}
|
|
||||||
link={`/projects/${t.id}`}
|
|
||||||
validateTicket={async (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
await put<HttpResponse<Ticket>>(
|
|
||||||
`${Constants.ticketsURI}/${t.id}/closed`,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
118
client/src/components/SignInSide.tsx
Normal file
118
client/src/components/SignInSide.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React from "react";
|
||||||
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||||
|
import Checkbox from "@material-ui/core/Checkbox";
|
||||||
|
import Link from "@material-ui/core/Link";
|
||||||
|
import Paper from "@material-ui/core/Paper";
|
||||||
|
import Grid from "@material-ui/core/Grid";
|
||||||
|
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
root: {
|
||||||
|
height: "100vh"
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
backgroundImage: "url(https://source.unsplash.com/daily?dev)",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.type === "light"
|
||||||
|
? theme.palette.grey[50]
|
||||||
|
: theme.palette.grey[900],
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center"
|
||||||
|
},
|
||||||
|
paper: {
|
||||||
|
margin: theme.spacing(8, 4),
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center"
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
backgroundColor: theme.palette.secondary.main
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
width: "100%",
|
||||||
|
marginTop: theme.spacing(1)
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
margin: theme.spacing(3, 0, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function SignInSide() {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container component="main" className={classes.root}>
|
||||||
|
<CssBaseline />
|
||||||
|
<Grid item xs={false} sm={4} md={7} className={classes.image} />
|
||||||
|
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Avatar className={classes.avatar}>
|
||||||
|
<LockOutlinedIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Sign in
|
||||||
|
</Typography>
|
||||||
|
<form className={classes.form} noValidate>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="email"
|
||||||
|
label="Email Address"
|
||||||
|
name="email"
|
||||||
|
autoComplete="email"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox value="remember" color="primary" />}
|
||||||
|
label="Remember me"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs>
|
||||||
|
<Link href="#" variant="body2">
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Link href="#" variant="body2">
|
||||||
|
{"Don't have an account? Sign Up"}
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
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 { 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[];
|
|
||||||
allProjects: Project[];
|
|
||||||
addButton?: Boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TicketList: FC<TicketListProps> = ({
|
|
||||||
tickets,
|
|
||||||
allProjects,
|
|
||||||
addButton = true
|
|
||||||
}) => {
|
|
||||||
const [filterText, setFilterText] = useState<string>("");
|
|
||||||
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
|
||||||
setFilterText("");
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = tickets.filter(
|
|
||||||
t =>
|
|
||||||
t.status !== "Done" &&
|
|
||||||
t.title.toLowerCase().includes(filterText.toLowerCase())
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="row valign-wrapper">
|
|
||||||
<NewTicketModal
|
|
||||||
handleClose={() => {
|
|
||||||
setShowNew(false);
|
|
||||||
}}
|
|
||||||
show={showNew}
|
|
||||||
allProjects={allProjects}
|
|
||||||
/>
|
|
||||||
<h3>Tickets</h3>
|
|
||||||
{addButton && (
|
|
||||||
<FloatingButton
|
|
||||||
color="indigo lighten-3"
|
|
||||||
size="small"
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<FilterBar
|
|
||||||
filterText={filterText}
|
|
||||||
handleChange={handleChange}
|
|
||||||
clearFilterText={clearFilterText}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col s12 grey lighten-1">
|
|
||||||
<ul>
|
|
||||||
{filteredTickets.length === 0 ? (
|
|
||||||
<HorizontalCard />
|
|
||||||
) : (
|
|
||||||
filteredTickets.map((t: Ticket) => (
|
|
||||||
<HorizontalCard
|
|
||||||
key={t.id}
|
|
||||||
title={t.title}
|
|
||||||
remainingDays={t.endingDate}
|
|
||||||
link={`/tickets/${t.id}`}
|
|
||||||
validateTicket={async (e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
await put<HttpResponse<Ticket>>(
|
|
||||||
`${Constants.ticketsURI}/${t.id}/closed`,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,21 +1,36 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Header } from "../components/Header";
|
import { Header } from "../components/Header";
|
||||||
import { Avatar } from "../components/Avatar";
|
import { UserAvatar } from "./Avatars/UserAvatar";
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
// makeStyles, Theme
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
presentation: string;
|
presentation: string;
|
||||||
picture: string;
|
picture: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
// root: {
|
||||||
|
// paddingTop: theme.spacing(2),
|
||||||
|
// flexGrow: 1,
|
||||||
|
// },
|
||||||
|
// }));
|
||||||
|
|
||||||
export const UserHeader: FC<IProps> = ({ fullName, presentation, picture }) => {
|
export const UserHeader: FC<IProps> = ({ fullName, presentation, picture }) => {
|
||||||
|
// const classes = useStyles();
|
||||||
return (
|
return (
|
||||||
<div className="row valign-wrapper">
|
// <div className={classes.root}>
|
||||||
<div className="col s2">
|
<Grid container>
|
||||||
<Avatar picture={picture} />
|
<Grid item xs={2}>
|
||||||
</div>
|
<UserAvatar picture={picture} alt="" />
|
||||||
<div className="col s10">
|
</Grid>
|
||||||
|
<Grid item xs>
|
||||||
<Header title={fullName} description={presentation} />
|
<Header title={fullName} description={presentation} />
|
||||||
</div>
|
</Grid>
|
||||||
</div>
|
</Grid>
|
||||||
|
// </div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
41
client/src/layouts/MainLayout.tsx
Normal file
41
client/src/layouts/MainLayout.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import ButtonAppBar from "../components/ButtonAppBar";
|
||||||
|
import Footer from "../components/Footer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function useStyles creates the css styles used in the following component.
|
||||||
|
*/
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
// root style allow for fixed footer
|
||||||
|
root: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
minHeight: "100vh",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MainLayout is the principal page layout. It mainly ensure the footer is fixed
|
||||||
|
* to the page bottom.
|
||||||
|
*
|
||||||
|
* @param children - The encapsulated component.
|
||||||
|
*/
|
||||||
|
const MainLayout: FC = ({ children }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<header>
|
||||||
|
<ButtonAppBar />
|
||||||
|
</header>
|
||||||
|
{/* <BreadCrumb /> */}
|
||||||
|
<CssBaseline />
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLayout;
|
||||||
43
client/src/layouts/PageLayout.tsx
Normal file
43
client/src/layouts/PageLayout.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { FC, ReactNode } from "react";
|
||||||
|
import { makeStyles, Theme, Container } from "@material-ui/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function useStyles creates the css styles used in the following component.
|
||||||
|
*/
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
// root style allow for fixed footer
|
||||||
|
header: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
header: ReactNode;
|
||||||
|
content: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PageLayout divide the page in 2 parts: Header and Content, to ensure cohesion.
|
||||||
|
*
|
||||||
|
* @param Header - The encapsulated component.
|
||||||
|
* @param Content - The encapsulated component.
|
||||||
|
*/
|
||||||
|
const PageLayout: FC<IProps> = ({ header, content }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md">
|
||||||
|
<div className={classes.header}>{header}</div>
|
||||||
|
<div className={classes.content}>{content}</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageLayout;
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LogInForm } from "../components/LogInForm";
|
// import { LogInForm } from "../components/LogInForm";
|
||||||
import { ProfileSelector } from "../components/ProfileSelector";
|
// import { ProfileSelector } from "../components/ProfileSelector";
|
||||||
|
import SignInSide from "../components/SignInSide";
|
||||||
|
|
||||||
export const HomePage: React.FC = () => {
|
export const HomePage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="section">
|
// <div className="section">
|
||||||
<div className="container center">
|
// <div className="container center">
|
||||||
<h1 className="center">Ticket Manager</h1>
|
// <h1 className="center">Ticket Manager</h1>
|
||||||
<div className="row">
|
// <div className="row">
|
||||||
<div className="col s6">
|
// <div className="col s6">
|
||||||
<ProfileSelector />
|
// <ProfileSelector />
|
||||||
</div>
|
// </div>
|
||||||
<div className="col s6">
|
// <div className="col s6">
|
||||||
<LogInForm />
|
// <LogInForm />
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
|
<SignInSide />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import React, { FC } from "react";
|
|
||||||
import { AppRouter } from "../utils/router";
|
|
||||||
import { NavBar } from "../components/Navbar";
|
|
||||||
|
|
||||||
const Layout: FC = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<header>
|
|
||||||
<NavBar />
|
|
||||||
</header>
|
|
||||||
{/* <BreadCrumb /> */}
|
|
||||||
<AppRouter />
|
|
||||||
{/* <Footer /> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Layout;
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
|
import PageLayout from "../layouts/PageLayout";
|
||||||
|
import { Header } from "../components/Header";
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
export const NotFoundPage: FC<IProps> = () => {
|
export const NotFoundPage: FC<IProps> = () => {
|
||||||
return (
|
return (
|
||||||
<div className="section">
|
<PageLayout
|
||||||
<p>error</p>
|
header={<Header title="Error page" description="Something went wrong" />}
|
||||||
</div>
|
content={<p>error</p>}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,27 @@
|
||||||
import React, { FC, useState } from "react";
|
import React, { FC, useState } from "react";
|
||||||
import ProjectVM from "../VM/ProjectVM";
|
import { Grid, makeStyles, Theme } from "@material-ui/core";
|
||||||
import { Header } from "../components/Header";
|
import { Header } from "../components/Header";
|
||||||
import { AvatarList } from "../components/AvatarList";
|
import { AvatarList } from "../components/Avatars/AvatarList";
|
||||||
import { ProgressBar } from "../components/ProgressBar";
|
import { ProgressBar } from "../components/Progress/ProgressBar";
|
||||||
import { TabRouter } from "../components/TabRouter";
|
import { FloatingButton } from "../components/Buttons/FloatingButton";
|
||||||
import { FloatingButton } from "../components/FloatingButton";
|
import { UsersModal } from "../components/Modals/UsersModal";
|
||||||
import { UsersModal } from "../components/UsersModal";
|
import { ProjectTabPanel } from "../components/Panels/ProjectTabPanel";
|
||||||
|
import ProjectVM from "../VM/ProjectVM";
|
||||||
|
import PageLayout from "../layouts/PageLayout";
|
||||||
|
import { ProgressInfo } from "../components/Progress/ProgressInfo";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
viewModel: ProjectVM;
|
viewModel: ProjectVM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
marginTop: theme.spacing(4),
|
||||||
|
marginBottom: theme.spacing(4),
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
||||||
const {
|
const {
|
||||||
// id,
|
// id,
|
||||||
|
|
@ -24,46 +35,65 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
||||||
ticketsTotalCount,
|
ticketsTotalCount,
|
||||||
remainingDays,
|
remainingDays,
|
||||||
files,
|
files,
|
||||||
activities,
|
// activities,
|
||||||
allProjects
|
allProjects,
|
||||||
} = viewModel;
|
} = viewModel;
|
||||||
|
|
||||||
const tabNames: string[] = ["Tickets", "Files"]; //, "Activity"];
|
const tabNames: string[] = ["Tickets"]; //, "Files", "Activity"];
|
||||||
const [showModal, setShowModal] = useState<boolean>(false);
|
const [showModal, setShowModal] = useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
const classes = useStyles();
|
||||||
<div className="section">
|
|
||||||
<div className="container">
|
const Content: FC = () => {
|
||||||
<Header title={title} description={description} />
|
return (
|
||||||
<div className="row valign-wrapper">
|
<>
|
||||||
<AvatarList users={users} />
|
<UsersModal
|
||||||
<FloatingButton
|
show={showModal}
|
||||||
icon="add"
|
users={users}
|
||||||
color="indigo lighten-3"
|
allUsers={allUsers}
|
||||||
size="small"
|
handleClose={() => setShowModal(false)}
|
||||||
onClick={() => setShowModal(true)}
|
/>
|
||||||
/>
|
|
||||||
<UsersModal
|
<Grid container>
|
||||||
show={showModal}
|
<Grid
|
||||||
users={users}
|
container
|
||||||
allUsers={allUsers}
|
direction="row"
|
||||||
handleClose={() => setShowModal(false)}
|
justify="flex-start"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<AvatarList users={users} />
|
||||||
|
<FloatingButton
|
||||||
|
icon="add"
|
||||||
|
color="default"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<div className={classes.root}>
|
||||||
|
<ProgressBar value={progression} />
|
||||||
|
<ProgressInfo
|
||||||
|
tasksDone={ticketsDone}
|
||||||
|
tasksTotalCount={ticketsTotalCount}
|
||||||
|
remainingDays={remainingDays}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar
|
<ProjectTabPanel
|
||||||
value={progression}
|
|
||||||
tasksDone={ticketsDone}
|
|
||||||
tasksTotalCount={ticketsTotalCount}
|
|
||||||
remainingDays={remainingDays}
|
|
||||||
/>
|
|
||||||
<TabRouter
|
|
||||||
tabNames={tabNames}
|
tabNames={tabNames}
|
||||||
tickets={tickets}
|
tickets={tickets}
|
||||||
files={files}
|
files={files}
|
||||||
activities={activities}
|
// activities={activities}
|
||||||
allProjects={allProjects}
|
allProjects={allProjects}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
</div>
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout
|
||||||
|
header={<Header title={title} description={description} />}
|
||||||
|
content={<Content />}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,33 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Header } from "../components/Header";
|
|
||||||
import { AvatarList } from "../components/AvatarList";
|
|
||||||
import { TicketVM } from "../VM/TicketVM";
|
|
||||||
import { getRemainingdays } from "../utils/methods";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { makeStyles, Theme, Grid, Typography } from "@material-ui/core";
|
||||||
|
import { Timer } from "@material-ui/icons";
|
||||||
|
import PageLayout from "../layouts/PageLayout";
|
||||||
|
import { TicketVM } from "../VM/TicketVM";
|
||||||
|
import { Header } from "../components/Header";
|
||||||
|
import { AvatarList } from "../components/Avatars/AvatarList";
|
||||||
|
import TicketChipsArray from "../components/Cards/TicketChipsArray";
|
||||||
|
import { getRemainingdays } from "../utils/methods";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
viewModel: TicketVM;
|
viewModel: TicketVM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
// flexGrow: 1,
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
margin: "auto",
|
||||||
|
maxWidth: 650,
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const TicketPage: FC<IProps> = ({ viewModel }) => {
|
export const TicketPage: FC<IProps> = ({ viewModel }) => {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
|
|
@ -19,61 +38,98 @@ export const TicketPage: FC<IProps> = ({ viewModel }) => {
|
||||||
status,
|
status,
|
||||||
category,
|
category,
|
||||||
impact,
|
impact,
|
||||||
difficulty
|
difficulty,
|
||||||
} = viewModel;
|
} = viewModel;
|
||||||
const daysToEnd: number = getRemainingdays(endingDate);
|
const daysToEnd: number = getRemainingdays(endingDate);
|
||||||
// let notes: string = "";
|
// let notes: string = "";
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
const Content: FC = () => {
|
||||||
<div className="section">
|
return (
|
||||||
<div className="container">
|
<>
|
||||||
<Header title={title} description={description} />
|
|
||||||
<AvatarList users={users} />
|
<AvatarList users={users} />
|
||||||
|
|
||||||
<div className="row section">
|
<div className={classes.root}>
|
||||||
<div className="col s9">
|
<Grid container>
|
||||||
<h5>
|
<Grid item xs={9}>
|
||||||
<b>In project: </b>{" "}
|
<Typography
|
||||||
<Link to={`/projects/${project.id}`}>{project.title}</Link>
|
variant="h5"
|
||||||
</h5>
|
component="h5"
|
||||||
</div>
|
className={classes.subtitle}
|
||||||
<div className="col s3">
|
>
|
||||||
<i className="left material-icons">timer</i>
|
<b>Project: </b>
|
||||||
<span>Due in {daysToEnd} days</span>
|
<Link to={`/projects/${project.id}`}>{project.title}</Link>
|
||||||
</div>
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs>
|
||||||
|
<Timer /> <span>Due in {daysToEnd} days</span>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="section white center">
|
<div className={classes.table}>
|
||||||
<div className="chip">
|
{/* <InfoTable
|
||||||
<span className="indigo-text">Status: </span> {status}
|
status={status}
|
||||||
{/* <i className="close material-icons">close</i> */}
|
category={category}
|
||||||
</div>
|
impact={impact}
|
||||||
|
difficulty={difficulty}
|
||||||
<div className="chip">
|
/> */}
|
||||||
<span className="orange-text">Category: </span> {category}
|
<TicketChipsArray
|
||||||
{/* <i className="close material-icons">close</i> */}
|
status={status}
|
||||||
</div>
|
category={category}
|
||||||
|
impact={impact}
|
||||||
<div className="chip">
|
difficulty={difficulty}
|
||||||
<span className="green-text">Impact: </span> {impact}
|
/>
|
||||||
{/* <i className="close material-icons">close</i> */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="chip">
|
|
||||||
<span className="red-text">Difficulty: </span> {difficulty}
|
|
||||||
{/* <i className="close material-icons">close</i> */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <textarea
|
{/* <textarea
|
||||||
id="notes"
|
id="notes"
|
||||||
className="materialize-textarea validate"
|
className="materialize-textarea validate"
|
||||||
value={notes}
|
value={notes}
|
||||||
// onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
// onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
// setDescription(e.target.value)
|
// setDescription(e.target.value)
|
||||||
// }
|
// }
|
||||||
></textarea> */}
|
></textarea> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
</div>
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<PageLayout
|
||||||
|
header={<Header title={title} description={description} />}
|
||||||
|
content={<Content />}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// interface InfoProps {
|
||||||
|
// status: string;
|
||||||
|
// category: string;
|
||||||
|
// impact: string;
|
||||||
|
// difficulty: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const InfoTable: FC<InfoProps> = (info: InfoProps) => {
|
||||||
|
// const classes = useStyles();
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <TableContainer component={Paper}>
|
||||||
|
// <Table className={classes.table} aria-label="simple table">
|
||||||
|
// <TableHead>
|
||||||
|
// <TableRow>
|
||||||
|
// <TableCell>Status</TableCell>
|
||||||
|
// <TableCell>Category</TableCell>
|
||||||
|
// <TableCell>Impact</TableCell>
|
||||||
|
// <TableCell>Difficulty</TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// </TableHead>
|
||||||
|
// <TableBody>
|
||||||
|
// <TableRow>
|
||||||
|
// <TableCell>{info.status}</TableCell>
|
||||||
|
// <TableCell>{info.category}</TableCell>
|
||||||
|
// <TableCell>{info.impact}</TableCell>
|
||||||
|
// <TableCell>{info.difficulty}</TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// </TableBody>
|
||||||
|
// </Table>
|
||||||
|
// </TableContainer>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,41 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { UserVM } from "../VM/UserVM";
|
import { UserVM } from "../VM/UserVM";
|
||||||
import { UserHeader } from "../components/UserHeader";
|
import { UserHeader } from "../components/UserHeader";
|
||||||
import { UserTabRouter } from "../components/UserTabRouter";
|
import { UserTabPanel } from "../components/Panels/UserTabPanel";
|
||||||
|
import PageLayout from "../layouts/PageLayout";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
viewModel: UserVM;
|
viewModel: UserVM;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (
|
||||||
<div className="section">
|
<PageLayout
|
||||||
<div className="container">
|
header={
|
||||||
<UserHeader
|
<UserHeader
|
||||||
picture={picture}
|
picture={picture}
|
||||||
fullName={fullName}
|
fullName={fullName}
|
||||||
presentation={presentation}
|
presentation={presentation}
|
||||||
/>
|
/>
|
||||||
<UserTabRouter
|
}
|
||||||
|
content={
|
||||||
|
<UserTabPanel
|
||||||
tabNames={tabNames}
|
tabNames={tabNames}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
tickets={tickets}
|
tickets={tickets}
|
||||||
|
allUsers={allUsers}
|
||||||
/>
|
/>
|
||||||
</div>
|
}
|
||||||
</div>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
3
client/src/types/enums/category.ts
Normal file
3
client/src/types/enums/category.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const Category: string[] = ["Product", "Tech", "Design", "Marketing", "Test"];
|
||||||
|
|
||||||
|
export default Category;
|
||||||
3
client/src/types/enums/difficulty.ts
Normal file
3
client/src/types/enums/difficulty.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const Difficulty: string[] = ["Easy", "Medium", "Hard"];
|
||||||
|
|
||||||
|
export default Difficulty;
|
||||||
3
client/src/types/enums/impact.ts
Normal file
3
client/src/types/enums/impact.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const Impact: string[] = ["High", "Medium", "Low"];
|
||||||
|
|
||||||
|
export default Impact;
|
||||||
24
client/src/utils/PrivateRoute.jsx
Normal file
24
client/src/utils/PrivateRoute.jsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Route } from "react-router-dom";
|
||||||
|
import { useAuth0 } from "../authentication/auth0";
|
||||||
|
|
||||||
|
export const PrivateRoute = ({ component: Component, path, ...rest }) => {
|
||||||
|
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loading || isAuthenticated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fn = async () => {
|
||||||
|
await loginWithRedirect({
|
||||||
|
appState: { targetUrl: window.location.pathname }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
fn();
|
||||||
|
}, [loading, isAuthenticated, loginWithRedirect, path]);
|
||||||
|
|
||||||
|
const render = props =>
|
||||||
|
isAuthenticated === true ? <Component {...props} /> : null;
|
||||||
|
|
||||||
|
return <Route path={path} render={render} {...rest} />;
|
||||||
|
};
|
||||||
|
|
@ -1,45 +1,37 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Router, Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import * as creacteHistory from "history";
|
|
||||||
import { HomeController } from "../controllers/HomeController";
|
import { HomeController } from "../controllers/HomeController";
|
||||||
import { ProjectController } from "../controllers/ProjectController";
|
import { ProjectController } from "../controllers/ProjectController";
|
||||||
import { UserController } from "../controllers/UserController";
|
import { UserController } from "../controllers/UserController";
|
||||||
import { TicketController } from "../controllers/TicketController";
|
import { TicketController } from "../controllers/TicketController";
|
||||||
import { NotFoundPage } from "../pages/NotFoundPage";
|
import { NotFoundPage } from "../pages/NotFoundPage";
|
||||||
// import { TestPage } from "../pages/TestPage";
|
|
||||||
|
|
||||||
export const history = creacteHistory.createBrowserHistory();
|
|
||||||
|
|
||||||
export const AppRouter = () => {
|
export const AppRouter = () => {
|
||||||
return (
|
return (
|
||||||
<Router history={history}>
|
<Switch>
|
||||||
<div className="grey lighten-3">
|
{/* <PrivateRoute exact path="/test">
|
||||||
<Switch>
|
<TestPage />
|
||||||
{/* <Route exact path="/">
|
</PrivateRoute> */}
|
||||||
<TestPage />
|
|
||||||
</Route> */}
|
|
||||||
|
|
||||||
<Route exact path="/">
|
<Route exact 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="/404">
|
<Route path="/404">
|
||||||
<NotFoundPage />
|
<NotFoundPage />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
|
||||||
</Router>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue