mirror of
https://github.com/rjNemo/ticket_manager
synced 2026-06-06 00:36:39 +00:00
pull last changes
This commit is contained in:
commit
e3f8d5df6c
79 changed files with 1369 additions and 1226 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -7,4 +7,4 @@ app.db*
|
|||
.DS_Store
|
||||
app.db
|
||||
client/node_modules
|
||||
Scripts/
|
||||
client/src/pages/TestPage.tsx
|
||||
|
|
@ -1,44 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
using TicketManager.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
[Route("api/v1/[controller]")]
|
||||
// [Authorize]
|
||||
[Route("api/v1/users")]
|
||||
[ApiController]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
private readonly IAppUserRepository _users;
|
||||
|
||||
public UsersController(AppDbContext context)
|
||||
public UsersController(IAppUserRepository users)
|
||||
{
|
||||
_context = context;
|
||||
_users = users;
|
||||
}
|
||||
|
||||
// GET: api/Users
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<AppUser>>> GetUsers()
|
||||
public async Task<IEnumerable<AppUser>> GetUsers()
|
||||
{
|
||||
return await getAllAppUsersAsync();
|
||||
return await _users.List();
|
||||
}
|
||||
|
||||
// GET: api/Users/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<AppUser>> GetUser(Guid id)
|
||||
{
|
||||
var user = await getAppUserByIdAsync(id);
|
||||
|
||||
var user = await _users.GetUser(id);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
@ -52,16 +50,13 @@ namespace TicketManager.Controllers
|
|||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(user).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
await _users.Update(user);
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!UserExists(id))
|
||||
if (!_users.Exists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
|
@ -70,7 +65,6 @@ namespace TicketManager.Controllers
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
|
@ -80,32 +74,27 @@ namespace TicketManager.Controllers
|
|||
[HttpPost]
|
||||
public async Task<ActionResult<AppUser>> PostUser(AppUser user)
|
||||
{
|
||||
_context.AppUsers.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _users.Add(user);
|
||||
return CreatedAtAction("GetUser", new { id = user.Id }, user);
|
||||
}
|
||||
|
||||
// DELETE: api/Users/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<AppUser>> DeleteUser(int id)
|
||||
public async Task<ActionResult<AppUser>> DeleteUser(Guid id)
|
||||
{
|
||||
var user = await _context.AppUsers.FindAsync(id);
|
||||
var user = await _users.GetUser(id);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.AppUsers.Remove(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _users.Delete(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
[HttpGet("{id}/projects")]
|
||||
public async Task<ActionResult<IEnumerable<Project>>> GetAppUserProjects(Guid id)
|
||||
{
|
||||
AppUser user = await getAppUserByIdAsync(id);
|
||||
AppUser user = await _users.GetUser(id);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest();
|
||||
|
|
@ -116,36 +105,12 @@ namespace TicketManager.Controllers
|
|||
[HttpGet("{id}/tickets/")]
|
||||
public async Task<ActionResult<IEnumerable<Ticket>>> GetAppUserTickets(Guid id)
|
||||
{
|
||||
AppUser user = await getAppUserByIdAsync(id);
|
||||
AppUser user = await _users.GetUser(id);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return user.GetTickets();
|
||||
}
|
||||
|
||||
private bool UserExists(Guid id)
|
||||
{
|
||||
return _context.AppUsers.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
private IQueryable<AppUser> appUserQuery()
|
||||
{
|
||||
return _context.AppUsers
|
||||
.Include(p => p.Assignments)
|
||||
.ThenInclude(a => a.Project)
|
||||
.ThenInclude(p => p.Tickets)
|
||||
.Include(p => p.Edits);
|
||||
}
|
||||
|
||||
private async Task<ActionResult<IEnumerable<AppUser>>> getAllAppUsersAsync()
|
||||
{
|
||||
return await appUserQuery().ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<AppUser> getAppUserByIdAsync(Guid id)
|
||||
{
|
||||
return await appUserQuery().FirstOrDefaultAsync(a => a.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
|
|
@ -10,6 +9,7 @@ using TicketManager.Models;
|
|||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class AssignmentsController : ControllerBase
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
|
|
@ -10,6 +9,7 @@ using TicketManager.Models;
|
|||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class FilesController : ControllerBase
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
using TicketManager.Models;
|
||||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
[Route("api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class HistoriesController : ControllerBase
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
|
||||
public HistoriesController(AppDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/Histories
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<History>>> GetEdits()
|
||||
{
|
||||
return await _context.Edits.ToListAsync();
|
||||
}
|
||||
|
||||
// GET: api/Histories/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<History>> GetHistory(int id)
|
||||
{
|
||||
var history = await _context.Edits.FindAsync(id);
|
||||
|
||||
if (history == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
// PUT: api/Histories/5
|
||||
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
|
||||
// more details see https://aka.ms/RazorPagesCRUD.
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutHistory(int id, History history)
|
||||
{
|
||||
if (id != history.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(history).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!HistoryExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/Histories
|
||||
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
|
||||
// more details see https://aka.ms/RazorPagesCRUD.
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<History>> PostHistory(History history)
|
||||
{
|
||||
_context.Edits.Add(history);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetHistory", new { id = history.Id }, history);
|
||||
}
|
||||
|
||||
// DELETE: api/Histories/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<History>> DeleteHistory(int id)
|
||||
{
|
||||
var history = await _context.Edits.FindAsync(id);
|
||||
if (history == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.Edits.Remove(history);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
private bool HistoryExists(int id)
|
||||
{
|
||||
return _context.Edits.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
|
|
@ -10,6 +9,7 @@ using TicketManager.Models;
|
|||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class NotesController : ControllerBase
|
||||
|
|
|
|||
|
|
@ -1,53 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
using TicketManager.Models;
|
||||
|
||||
using TicketManager.DTO;
|
||||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
// [Authorize(Roles = "Admin")]
|
||||
// [Authorize]
|
||||
[Produces("application/json")]
|
||||
[Route("api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class ProjectsController : ControllerBase
|
||||
{
|
||||
private readonly IProjectRepository _projectRepo;
|
||||
|
||||
public ProjectsController(IProjectRepository projectRepo)
|
||||
private IProjectRepository _projects;
|
||||
public ProjectsController(IProjectRepository context)
|
||||
{
|
||||
_projectRepo = projectRepo;
|
||||
_projects = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all existing projects.
|
||||
/// Returns all projects stored in the database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// GET: api/Projects
|
||||
/// GET: api/v1/Projects
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Returns all existing projects</response>
|
||||
/// <response code="200">Returns a list of projects</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IEnumerable<Project>> GetProjects()
|
||||
{
|
||||
return await _projectRepo.List();
|
||||
|
||||
return await _projects.List();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific project.
|
||||
/// Locate a specific project stored in the database by its Id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// GET: api/Projects/2
|
||||
/// GET: api/v1/Projects/2
|
||||
///
|
||||
/// </remarks>
|
||||
/// <param name="id">Identifier of the ressource</param>
|
||||
|
|
@ -56,177 +57,167 @@ namespace TicketManager.Controllers
|
|||
[HttpGet("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Project>> GetProject(int id)
|
||||
public async Task<ActionResult<ProjectDTO>> GetProject(int id)
|
||||
{
|
||||
Project project = await _projectRepo.Get(id);
|
||||
if (project == null) { return NotFound(); }
|
||||
return project;
|
||||
Project project = await _projects.Get(id);
|
||||
if (project == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return new ProjectDTO(project);
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates a specific project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// PUT: api/Projects/3
|
||||
// /// {
|
||||
// /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// /// "firstName": "Thomas",
|
||||
// /// "lastName": "Price",
|
||||
// /// "presentation": "New Team?!",
|
||||
// /// "email": "tp@mail.com",
|
||||
// /// "phone": "0198237645"
|
||||
// /// }
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns the modified project</response>
|
||||
// /// <response code="204">Request was succesful but no content is changed</response>
|
||||
// /// <response code="404">If the required project is null</response>
|
||||
// [HttpPut("{id}")]
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// public async Task<IActionResult> PutProject(int id, Project project)
|
||||
// {
|
||||
// if (id != project.Id) { return BadRequest(); }
|
||||
/// <summary>
|
||||
/// Updates the specific project with Id.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// PUT: api/v1/Projects/3
|
||||
/// {
|
||||
/// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
/// "firstName": "Thomas",
|
||||
/// "lastName": "Price",
|
||||
/// "presentation": "New Team?!",
|
||||
/// "email": "tp@mail.com",
|
||||
/// "phone": "0198237645"
|
||||
/// }
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="204">Request was succesful but no content is changed</response>
|
||||
/// <response code="404">If the required project is null</response>
|
||||
[HttpPut("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> PutProject(int id, Project project)
|
||||
{
|
||||
if (id != project.Id) { return BadRequest(); }
|
||||
try
|
||||
{
|
||||
await _projects.Update(project);
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!_projects.Exists(id)) { return NotFound(); }
|
||||
else { throw; }
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// try
|
||||
// {
|
||||
// await _projectRepo.Update(project);
|
||||
// }
|
||||
// catch (DbUpdateConcurrencyException)
|
||||
// {
|
||||
// if (!_projectRepo.Exists(id))
|
||||
// {
|
||||
// return NotFound();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// throw;
|
||||
// }
|
||||
// }
|
||||
/// <summary>
|
||||
/// Creates a project.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// POST: api/v1/Projects/
|
||||
/// {
|
||||
/// "firstName": "Thomas",
|
||||
/// "lastName": "Price",
|
||||
/// "presentation": "New Team?!",
|
||||
/// "email": "tp@mail.com",
|
||||
/// "phone": "0198237645"
|
||||
/// }
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="201">Returns the created project</response>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Project>> PostProject(Project project)
|
||||
{
|
||||
if (!ModelState.IsValid) { return BadRequest(); }
|
||||
await _projects.Add(project);
|
||||
return CreatedAtAction("GetProject", new { id = project.Id }, project);
|
||||
}
|
||||
|
||||
// return NoContent();
|
||||
// }
|
||||
/// <summary>
|
||||
/// Deletes the project identified by its Id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// DELETE: api/v1/Projects/5
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Returns the deleted project</response>
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteProject(int id)
|
||||
{
|
||||
var project = await _projects.Get(id);
|
||||
if (project == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
await _projects.Delete(project);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Creates a project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// POST: api/Projects/
|
||||
// /// {
|
||||
// /// "firstName": "Thomas",
|
||||
// /// "lastName": "Price",
|
||||
// /// "presentation": "New Team?!",
|
||||
// /// "email": "tp@mail.com",
|
||||
// /// "phone": "0198237645"
|
||||
// /// }
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="201">Returns the created project</response>
|
||||
// [HttpPost]
|
||||
// [ProducesResponseType(StatusCodes.Status201Created)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// public async Task<ActionResult<Project>> PostProject(Project project)
|
||||
// {
|
||||
// if (!ModelState.IsValid) { return BadRequest(); }
|
||||
// await _projectRepo.AddAsync(project);
|
||||
/// <summary>
|
||||
/// Gets a project members.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// GET: api/v1/Projects/5/Members
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Returns the project members as a list of users.</response>
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[HttpGet("{id}/members")]
|
||||
public async Task<ActionResult<List<AppUser>>> GetProjectMembers(int id)
|
||||
{
|
||||
var project = await _projects.Get(id);
|
||||
if (project == null)
|
||||
{ return NotFound(); }
|
||||
return project.GetMembers();
|
||||
}
|
||||
|
||||
// return CreatedAtAction("GetProject", new { id = project.Id }, project);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Deletes a project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// DELETE: api/Projects/5
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns the deleted project</response>
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// [HttpDelete("{id}")]
|
||||
// public async Task<ActionResult<Project>> DeleteProject(int id)
|
||||
// {
|
||||
// var project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null)
|
||||
// {
|
||||
// return NotFound();
|
||||
// }
|
||||
// await _projectRepo.DeleteAsync(id);
|
||||
// return project;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets a project members.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// GET: api/Projects/5/Members
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns the project members</response>
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// [HttpGet("{id}/members")]
|
||||
// public async Task<ActionResult<List<AppUser>>> GetProjectMembers(int id)
|
||||
// {
|
||||
// Project project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null)
|
||||
// { return NotFound(); }
|
||||
// return project.GetMembers();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates a project members.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// PUT: api/Projects/5/Members
|
||||
// /// {
|
||||
// /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// /// "firstName": "Thomas",
|
||||
// /// "lastName": "Price",
|
||||
// /// "presentation": "New Team?!",
|
||||
// /// "email": "tp@mail.com",
|
||||
// /// "phone": "0198237645"
|
||||
// /// }
|
||||
// /// </remarks>
|
||||
// /// <response code="204">No content</response>
|
||||
// [ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// [HttpPut("{id}/members")]
|
||||
// public async Task<ActionResult<Project>> SetProjectMembers(int id, List<AppUser> projectMembers)
|
||||
// {
|
||||
// Project project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null)
|
||||
// {
|
||||
// return NotFound();
|
||||
// }
|
||||
// project.SetMembers(projectMembers);
|
||||
// try
|
||||
// {
|
||||
// await _projectRepo.UpdateAsync(project);
|
||||
// }
|
||||
// catch (DbUpdateException /* ex */)
|
||||
// {
|
||||
// //Log the error (uncomment ex variable name and write a log.)
|
||||
// ModelState.AddModelError("", "Unable to save changes. " +
|
||||
// "Try again, and if the problem persists, " +
|
||||
// "see your system administrator.");
|
||||
// }
|
||||
// return NoContent();
|
||||
// }
|
||||
/// <summary>
|
||||
/// Updates a project members.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// PUT: api/v1/Projects/5/Members
|
||||
/// {
|
||||
/// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
/// "firstName": "Thomas",
|
||||
/// "lastName": "Price",
|
||||
/// "presentation": "New Team?!",
|
||||
/// "email": "tp@mail.com",
|
||||
/// "phone": "0198237645"
|
||||
/// }
|
||||
/// </remarks>
|
||||
/// <response code="204">No content</response>
|
||||
/// <response code="404">Not Found</response>
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[HttpPut("{id}/members")]
|
||||
public async Task<ActionResult<Project>> SetProjectMembers(int id, List<AppUser> projectMembers)
|
||||
{
|
||||
Project project = await _projects.Get(id);
|
||||
if (project == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
project.SetMembers(projectMembers);
|
||||
try
|
||||
{
|
||||
await _projects.Update(project);
|
||||
}
|
||||
catch (DbUpdateException /* ex */)
|
||||
{
|
||||
//Log the error (uncomment ex variable name and write a log.)
|
||||
ModelState.AddModelError("", "Unable to save changes. " +
|
||||
"Try again, and if the problem persists, " +
|
||||
"see your system administrator.");
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// // /// <summary>
|
||||
// // /// Assign a user to a project.
|
||||
|
|
@ -234,7 +225,7 @@ namespace TicketManager.Controllers
|
|||
// // /// <remarks>
|
||||
// // /// Sample request:
|
||||
// // ///
|
||||
// // /// POST: api/Projects/addmembers
|
||||
// // /// POST: api/v1/Projects/addmembers
|
||||
// // /// [{
|
||||
// // /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// // /// "firstName": "Thomas",
|
||||
|
|
@ -277,7 +268,7 @@ namespace TicketManager.Controllers
|
|||
// // /// <remarks>
|
||||
// // /// Sample request:
|
||||
// // ///
|
||||
// // /// PUT: api/Projects/removemembers
|
||||
// // /// PUT: api/v1/Projects/removemembers
|
||||
// // /// [{
|
||||
// // /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// // /// "firstName": "Thomas",
|
||||
|
|
@ -309,9 +300,5 @@ namespace TicketManager.Controllers
|
|||
// // }
|
||||
// // return NoContent();
|
||||
// // }
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,317 +0,0 @@
|
|||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Net.Mime;
|
||||
// using System.Threading.Tasks;
|
||||
// using Microsoft.AspNetCore.Http;
|
||||
// using Microsoft.AspNetCore.Mvc;
|
||||
// using Microsoft.EntityFrameworkCore;
|
||||
// using TicketManager.Data;
|
||||
// using TicketManager.Models;
|
||||
|
||||
|
||||
// namespace TicketManager.Controllers
|
||||
// {
|
||||
// [Produces("application/json")]
|
||||
// [Route("api/v1/[controller]")]
|
||||
// [ApiController]
|
||||
// public class ProjectsController : ControllerBase
|
||||
// {
|
||||
// private readonly IProjectRepository _projectRepo;
|
||||
|
||||
// public ProjectsController(IProjectRepository projectRepo)
|
||||
// {
|
||||
// _projectRepo = projectRepo;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns all existing projects.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// GET: api/Projects
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns all existing projects</response>
|
||||
// [HttpGet]
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// public async Task<IEnumerable<Project>> GetProjects()
|
||||
// {
|
||||
// return await _projectRepo.ListAsync();
|
||||
// // GetAllProjectsAsync();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Returns a specific project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// GET: api/Projects/2
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns a specific project</response>
|
||||
// /// <response code="404">If the required project is null</response>
|
||||
// [HttpGet("{id}")]
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// public async Task<ActionResult<Project>> GetProject(int id)
|
||||
// {
|
||||
// Project project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null) { return NotFound(); }
|
||||
// return project;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates a specific project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// PUT: api/Projects/3
|
||||
// /// {
|
||||
// /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// /// "firstName": "Thomas",
|
||||
// /// "lastName": "Price",
|
||||
// /// "presentation": "New Team?!",
|
||||
// /// "email": "tp@mail.com",
|
||||
// /// "phone": "0198237645"
|
||||
// /// }
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns the modified project</response>
|
||||
// /// <response code="204">Request was succesful but no content is changed</response>
|
||||
// /// <response code="404">If the required project is null</response>
|
||||
// [HttpPut("{id}")]
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// public async Task<IActionResult> PutProject(int id, Project project)
|
||||
// {
|
||||
// if (id != project.Id) { return BadRequest(); }
|
||||
|
||||
// try
|
||||
// {
|
||||
// await _projectRepo.UpdateAsync(project);
|
||||
// }
|
||||
// catch (DbUpdateConcurrencyException)
|
||||
// {
|
||||
// if (!_projectRepo.Exists(id))
|
||||
// {
|
||||
// return NotFound();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// throw;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return NoContent();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Creates a project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// POST: api/Projects/
|
||||
// /// {
|
||||
// /// "firstName": "Thomas",
|
||||
// /// "lastName": "Price",
|
||||
// /// "presentation": "New Team?!",
|
||||
// /// "email": "tp@mail.com",
|
||||
// /// "phone": "0198237645"
|
||||
// /// }
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="201">Returns the created project</response>
|
||||
// [HttpPost]
|
||||
// [ProducesResponseType(StatusCodes.Status201Created)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// public async Task<ActionResult<Project>> PostProject(Project project)
|
||||
// {
|
||||
// if (!ModelState.IsValid) { return BadRequest(); }
|
||||
// await _projectRepo.AddAsync(project);
|
||||
|
||||
// return CreatedAtAction("GetProject", new { id = project.Id }, project);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Deletes a project.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// DELETE: api/Projects/5
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns the deleted project</response>
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// [HttpDelete("{id}")]
|
||||
// public async Task<ActionResult<Project>> DeleteProject(int id)
|
||||
// {
|
||||
// var project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null)
|
||||
// {
|
||||
// return NotFound();
|
||||
// }
|
||||
// await _projectRepo.DeleteAsync(id);
|
||||
// return project;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Gets a project members.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// GET: api/Projects/5/Members
|
||||
// ///
|
||||
// /// </remarks>
|
||||
// /// <response code="200">Returns the project members</response>
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// [HttpGet("{id}/members")]
|
||||
// public async Task<ActionResult<List<AppUser>>> GetProjectMembers(int id)
|
||||
// {
|
||||
// Project project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null)
|
||||
// { return NotFound(); }
|
||||
// return project.GetMembers();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// Updates a project members.
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// Sample request:
|
||||
// ///
|
||||
// /// PUT: api/Projects/5/Members
|
||||
// /// {
|
||||
// /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// /// "firstName": "Thomas",
|
||||
// /// "lastName": "Price",
|
||||
// /// "presentation": "New Team?!",
|
||||
// /// "email": "tp@mail.com",
|
||||
// /// "phone": "0198237645"
|
||||
// /// }
|
||||
// /// </remarks>
|
||||
// /// <response code="204">No content</response>
|
||||
// [ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// [HttpPut("{id}/members")]
|
||||
// public async Task<ActionResult<Project>> SetProjectMembers(int id, List<AppUser> projectMembers)
|
||||
// {
|
||||
// Project project = await _projectRepo.GetByIdAsync(id);
|
||||
// if (project == null)
|
||||
// {
|
||||
// return NotFound();
|
||||
// }
|
||||
// project.SetMembers(projectMembers);
|
||||
// try
|
||||
// {
|
||||
// await _projectRepo.UpdateAsync(project);
|
||||
// }
|
||||
// catch (DbUpdateException /* ex */)
|
||||
// {
|
||||
// //Log the error (uncomment ex variable name and write a log.)
|
||||
// ModelState.AddModelError("", "Unable to save changes. " +
|
||||
// "Try again, and if the problem persists, " +
|
||||
// "see your system administrator.");
|
||||
// }
|
||||
// return NoContent();
|
||||
// }
|
||||
|
||||
// // /// <summary>
|
||||
// // /// Assign a user to a project.
|
||||
// // /// </summary>
|
||||
// // /// <remarks>
|
||||
// // /// Sample request:
|
||||
// // ///
|
||||
// // /// POST: api/Projects/addmembers
|
||||
// // /// [{
|
||||
// // /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// // /// "firstName": "Thomas",
|
||||
// // /// "lastName": "Price",
|
||||
// // /// "presentation": "New Team?!",
|
||||
// // /// "email": "tp@mail.com",
|
||||
// // /// "phone": "0198237645"
|
||||
// // /// }]
|
||||
// // ///
|
||||
// // /// </remarks>
|
||||
// // /// <response code="204">Returns the created project</response>
|
||||
// // [ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
// // [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// // [HttpPut("{id}/addMembers")]
|
||||
// // public async Task<ActionResult<Project>> AddMembersToProject(int id, List<AppUser> usersToAdd)
|
||||
// // {
|
||||
// // if (usersToAdd == null)
|
||||
// // {
|
||||
// // return BadRequest();
|
||||
// // }
|
||||
// // Project project = await GetProjectByIdAsync(id);
|
||||
// // project.AddMembers(usersToAdd);
|
||||
// // try
|
||||
// // {
|
||||
// // await _context.SaveChangesAsync();
|
||||
// // }
|
||||
// // catch (DbUpdateException /* ex */)
|
||||
// // {
|
||||
// // //Log the error (uncomment ex variable name and write a log.)
|
||||
// // ModelState.AddModelError("", "Unable to save changes. " +
|
||||
// // "Try again, and if the problem persists, " +
|
||||
// // "see your system administrator.");
|
||||
// // }
|
||||
// // return NoContent();
|
||||
// // }
|
||||
|
||||
// // /// <summary>
|
||||
// // /// Remove a user to a project.
|
||||
// // /// </summary>
|
||||
// // /// <remarks>
|
||||
// // /// Sample request:
|
||||
// // ///
|
||||
// // /// PUT: api/Projects/removemembers
|
||||
// // /// [{
|
||||
// // /// "id": "357727fd-5262-4522-b8a3-38271d43de84",
|
||||
// // /// "firstName": "Thomas",
|
||||
// // /// "lastName": "Price",
|
||||
// // /// "presentation": "New Team?!",
|
||||
// // /// "email": "tp@mail.com",
|
||||
// // /// "phone": "0198237645"
|
||||
// // /// }]
|
||||
// // ///
|
||||
// // /// </remarks>
|
||||
// // /// <response code="204">Returns the created project</response>
|
||||
// // [ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
// // [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// // [HttpPut("{id}/removeMembers")]
|
||||
// // public async Task<ActionResult<Project>> RemoveMembersFromProject(int id, List<AppUser> usersToRemove)
|
||||
// // {
|
||||
// // Project project = await GetProjectByIdAsync(id);
|
||||
// // project.RemoveMembers(usersToRemove);
|
||||
// // try
|
||||
// // {
|
||||
// // await _context.SaveChangesAsync();
|
||||
// // }
|
||||
// // catch (DbUpdateException /* ex */)
|
||||
// // {
|
||||
// // //Log the error (uncomment ex variable name and write a log.)
|
||||
// // ModelState.AddModelError("", "Unable to save changes. " +
|
||||
// // "Try again, and if the problem persists, " +
|
||||
// // "see your system administrator.");
|
||||
// // }
|
||||
// // return NoContent();
|
||||
// // }
|
||||
|
||||
|
||||
|
||||
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TicketManager.Data;
|
||||
|
|
@ -10,35 +8,34 @@ using TicketManager.Models;
|
|||
|
||||
namespace TicketManager.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("api/v1/[controller]")]
|
||||
[ApiController]
|
||||
public class TicketsController : ControllerBase
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
private readonly ITicketRepository _tickets;
|
||||
|
||||
public TicketsController(AppDbContext context)
|
||||
public TicketsController(ITicketRepository tickets)
|
||||
{
|
||||
_context = context;
|
||||
_tickets = tickets;
|
||||
}
|
||||
|
||||
// GET: api/Tickets
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<Ticket>>> GetTickets()
|
||||
public async Task<IEnumerable<Ticket>> GetTickets()
|
||||
{
|
||||
return await getAllTicketsAsync();
|
||||
return await _tickets.List();
|
||||
}
|
||||
|
||||
// GET: api/Tickets/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<Ticket>> GetTicket(int id)
|
||||
{
|
||||
var ticket = await getTicketByIdAsync(id);
|
||||
|
||||
var ticket = await _tickets.Get(id);
|
||||
if (ticket == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
|
|
@ -52,16 +49,13 @@ namespace TicketManager.Controllers
|
|||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(ticket).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
await _tickets.Update(ticket);
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TicketExists(id))
|
||||
if (!_tickets.Exists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
|
@ -70,7 +64,6 @@ namespace TicketManager.Controllers
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +73,7 @@ namespace TicketManager.Controllers
|
|||
[HttpPost]
|
||||
public async Task<ActionResult<Ticket>> PostTicket(Ticket ticket)
|
||||
{
|
||||
_context.Tickets.Add(ticket);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _tickets.Add(ticket);
|
||||
return CreatedAtAction("GetTicket", new { id = ticket.Id }, ticket);
|
||||
}
|
||||
|
||||
|
|
@ -90,59 +81,28 @@ namespace TicketManager.Controllers
|
|||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult<Ticket>> DeleteTicket(int id)
|
||||
{
|
||||
var ticket = await _context.Tickets.FindAsync(id);
|
||||
var ticket = await _tickets.Get(id);
|
||||
if (ticket == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.Tickets.Remove(ticket);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await _tickets.Delete(ticket);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
[HttpGet("{id}/assignees")]
|
||||
public async Task<ActionResult<List<AppUser>>> GetTicketAssignees(int id)
|
||||
{
|
||||
Ticket ticket = await getTicketByIdAsync(id);
|
||||
Ticket ticket = await _tickets.Get(id);
|
||||
return ticket.GetAssignees();
|
||||
}
|
||||
|
||||
[HttpPut("{id}/closed")]
|
||||
public async Task<ActionResult> CloseTicket(int id)
|
||||
public async Task<IActionResult> CloseTicket(int id)
|
||||
{
|
||||
Ticket ticket = await getTicketByIdAsync(id);
|
||||
Ticket ticket = await _tickets.Get(id);
|
||||
ticket.Close();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private bool TicketExists(int id)
|
||||
{
|
||||
return _context.Tickets.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
private IQueryable<Ticket> ticketQuery() // problem with link
|
||||
{
|
||||
return _context.Tickets
|
||||
.Include(p => p.Project)
|
||||
.ThenInclude(a => a.Assignments)
|
||||
.ThenInclude(p => p.User)
|
||||
// .Include(p => p.Edits)
|
||||
// .Include(p => p.Notes)
|
||||
// .Include(p => p.Files)
|
||||
// .Include(p => p.Creator)
|
||||
;
|
||||
}
|
||||
|
||||
private async Task<ActionResult<IEnumerable<Ticket>>> getAllTicketsAsync()
|
||||
{
|
||||
return await ticketQuery().ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<Ticket> getTicketByIdAsync(int id)
|
||||
{
|
||||
return await ticketQuery().FirstOrDefaultAsync(a => a.Id == id);
|
||||
return await PutTicket(ticket.Id, ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace TicketManager.Data
|
|||
public DbSet<AppUser> AppUsers { get; set; }
|
||||
public DbSet<Ticket> Tickets { get; set; }
|
||||
public DbSet<Assignment> Assignments { get; set; }
|
||||
public DbSet<History> Edits { get; set; }
|
||||
public DbSet<Activity> Activities { get; set; }
|
||||
public DbSet<Note> Notes { get; set; }
|
||||
public DbSet<File> Files { get; set; }
|
||||
|
||||
|
|
|
|||
37
Data/AppUserRepository.cs
Normal file
37
Data/AppUserRepository.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System.Threading.Tasks;
|
||||
using TicketManager.Models;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TicketManager.Data
|
||||
{
|
||||
public class AppUserRepository : GenericRepository<AppUser>, IAppUserRepository
|
||||
{
|
||||
private readonly IQueryable<AppUser> _query;
|
||||
public AppUserRepository(AppDbContext context) : base(context)
|
||||
{
|
||||
_query = _dbSet
|
||||
.Include(p => p.Assignments)
|
||||
.ThenInclude(a => a.Project)
|
||||
.ThenInclude(p => p.Tickets)
|
||||
.Include(p => p.Activities);
|
||||
}
|
||||
|
||||
public async Task<AppUser> GetUser(Guid id)
|
||||
{
|
||||
return await _query.FirstOrDefaultAsync(p => p.Id == id);
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<AppUser>> List()
|
||||
{
|
||||
return await _query.ToListAsync();
|
||||
}
|
||||
|
||||
public bool Exists(Guid id)
|
||||
{
|
||||
return _dbSet.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,16 +17,18 @@ namespace TicketManager.Data
|
|||
_dbSet = _context.Set<T>();
|
||||
}
|
||||
|
||||
public void Add(T entity)
|
||||
public async Task<int> Add(T entity)
|
||||
{
|
||||
_dbSet.Add(entity);
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public void Delete(T entity)
|
||||
public async Task<int> Delete(T entity)
|
||||
{
|
||||
if (_context.Entry(entity).State == EntityState.Detached)
|
||||
{ _dbSet.Attach(entity); }
|
||||
_dbSet.Remove(entity);
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<T>> Find(int id, Expression<Func<T, bool>> expr)
|
||||
|
|
@ -43,10 +45,11 @@ namespace TicketManager.Data
|
|||
return await _dbSet.AsNoTracking().ToListAsync();
|
||||
}
|
||||
|
||||
public void Update(T entity)
|
||||
public async Task<int> Update(T entity)
|
||||
{
|
||||
_dbSet.Attach(entity);
|
||||
_context.Entry(entity).State = EntityState.Modified;
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Data/Interfaces/IAppUserRepository.cs
Normal file
12
Data/Interfaces/IAppUserRepository.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TicketManager.Models;
|
||||
|
||||
namespace TicketManager.Data
|
||||
{
|
||||
public interface IAppUserRepository : IGenericRepository<AppUser>
|
||||
{
|
||||
Task<AppUser> GetUser(Guid id);
|
||||
bool Exists(Guid id);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,10 +11,10 @@ namespace TicketManager.Data
|
|||
Task<T> Get(int id);
|
||||
Task<IEnumerable<T>> Find(int id, Expression<Func<T, bool>> expr);
|
||||
|
||||
void Add(T entity);
|
||||
Task<int> Add(T entity);
|
||||
|
||||
void Update(T entity);
|
||||
Task<int> Update(T entity);
|
||||
|
||||
void Delete(T entity);
|
||||
Task<int> Delete(T entity);
|
||||
}
|
||||
}
|
||||
11
Data/Interfaces/ITicketRepository.cs
Normal file
11
Data/Interfaces/ITicketRepository.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using TicketManager.Models;
|
||||
|
||||
namespace TicketManager.Data
|
||||
{
|
||||
public interface ITicketRepository : IGenericRepository<Ticket>
|
||||
{
|
||||
bool Exists(int id);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ namespace TicketManager.Data
|
|||
public interface IUnitOfWork : IDisposable
|
||||
{
|
||||
IProjectRepository Projects { get; }
|
||||
IAppUserRepository AppUsers { get; }
|
||||
ITicketRepository Tickets { get; }
|
||||
Task<int> Complete();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// using System.Threading.Tasks;
|
||||
// using TicketManager.Models;
|
||||
// using System.Linq;
|
||||
// using Microsoft.EntityFrameworkCore;
|
||||
// using System.Collections.Generic;
|
||||
// using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
// namespace TicketManager.Data
|
||||
// {
|
||||
// public class ProjectRepository : IProjectRepository
|
||||
// {
|
||||
// private readonly AppDbContext _context;
|
||||
// private readonly IQueryable<Project> _query;
|
||||
// public ProjectRepository(AppDbContext context)
|
||||
// {
|
||||
// _context = context;
|
||||
// _query = _context.Projects
|
||||
// .Include(p => p.Assignments)
|
||||
// .ThenInclude(a => a.User)
|
||||
// .Include(p => p.Tickets)
|
||||
// .Include(p => p.Manager)
|
||||
// .Include(p => p.Files);
|
||||
// }
|
||||
|
||||
// public Task AddAsync(Project project)
|
||||
// {
|
||||
// _context.Projects.Add(project);
|
||||
// return _context.SaveChangesAsync();
|
||||
// }
|
||||
|
||||
// public async Task<int> DeleteAsync(int id)
|
||||
// {
|
||||
// Project project = await GetByIdAsync(id);
|
||||
// _context.Projects.Remove(project);
|
||||
// return await _context.SaveChangesAsync();
|
||||
// }
|
||||
|
||||
// public async Task<Project> GetByIdAsync(int id)
|
||||
// {
|
||||
// return await _query.FirstOrDefaultAsync(p => p.Id == id);
|
||||
// }
|
||||
|
||||
// public async Task<IEnumerable<Project>> ListAsync()
|
||||
// {
|
||||
// return await _query.ToListAsync();
|
||||
// }
|
||||
|
||||
// public Task UpdateAsync(Project project)
|
||||
// {
|
||||
// _context.Entry(project).State = EntityState.Modified;
|
||||
// return _context.SaveChangesAsync();
|
||||
// }
|
||||
// public bool Exists(int id)
|
||||
// { return _context.Projects.Any(e => e.Id == id); }
|
||||
|
||||
// }
|
||||
// }
|
||||
|
|
@ -15,8 +15,7 @@ namespace TicketManager.Data
|
|||
.Include(p => p.Assignments).ThenInclude(a => a.User)
|
||||
.Include(p => p.Tickets)
|
||||
.Include(p => p.Manager)
|
||||
.Include(p => p.Files)
|
||||
.AsNoTracking();
|
||||
.Include(p => p.Files);
|
||||
}
|
||||
|
||||
public override async Task<Project> Get(int id)
|
||||
|
|
@ -30,7 +29,9 @@ namespace TicketManager.Data
|
|||
}
|
||||
|
||||
public bool Exists(int id)
|
||||
{ return _dbSet.Any(e => e.Id == id); }
|
||||
{
|
||||
return _dbSet.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUser>> GetMembers(int id)
|
||||
{
|
||||
|
|
|
|||
40
Data/TicketRepository.cs
Normal file
40
Data/TicketRepository.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using TicketManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace TicketManager.Data
|
||||
{
|
||||
public class TicketRepository : GenericRepository<Ticket>, ITicketRepository
|
||||
{
|
||||
private IQueryable<Ticket> _query;
|
||||
public TicketRepository(AppDbContext context) : base(context)
|
||||
{
|
||||
_query = _dbSet
|
||||
.Include(p => p.Project)
|
||||
.ThenInclude(a => a.Assignments)
|
||||
.ThenInclude(p => p.User)
|
||||
// .Include(p => p.Edits)
|
||||
// .Include(p => p.Notes)
|
||||
// .Include(p => p.Files)
|
||||
// .Include(p => p.Creator)
|
||||
;
|
||||
}
|
||||
|
||||
public override async Task<Ticket> Get(int id)
|
||||
{
|
||||
return await _query.FirstOrDefaultAsync(p => p.Id == id);
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<Ticket>> List()
|
||||
{
|
||||
return await _query.ToListAsync();
|
||||
}
|
||||
|
||||
public bool Exists(int id)
|
||||
{
|
||||
return _dbSet.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,15 +10,20 @@ namespace TicketManager.Data
|
|||
{
|
||||
_context = context;
|
||||
Projects = new ProjectRepository(_context);
|
||||
Tickets = new TicketRepository(_context);
|
||||
AppUsers = new AppUserRepository(_context);
|
||||
}
|
||||
|
||||
public IProjectRepository Projects { get; private set; }
|
||||
|
||||
public IAppUserRepository AppUsers { get; private set; }
|
||||
|
||||
public ITicketRepository Tickets { get; private set; }
|
||||
|
||||
public async Task<int> Complete()
|
||||
{
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DisposeAsync();
|
||||
|
|
|
|||
47
DataTransfertObjects/ProjectDTO.cs
Normal file
47
DataTransfertObjects/ProjectDTO.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TicketManager.Models;
|
||||
|
||||
namespace TicketManager.DTO
|
||||
{
|
||||
public class ProjectDTO
|
||||
{
|
||||
public ProjectDTO(Project project)
|
||||
{
|
||||
Id = project.Id;
|
||||
Title = project.Title;
|
||||
Description = project.Description;
|
||||
CreatedAt = project.CreatedAt;
|
||||
Progression = project.Progression;
|
||||
Status = project.Status.ToString();
|
||||
Manager = project.Manager;
|
||||
Users = project.GetMembers();
|
||||
Tickets = project.Tickets;
|
||||
Activities = project.Activities;
|
||||
Files = project.Files;
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; private set; } = DateTime.Now;
|
||||
|
||||
public DateTime PlannedEnding { get; set; }
|
||||
|
||||
public decimal Progression { get; set; }
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public AppUser Manager { get; set; }
|
||||
public List<AppUser> Users { get; set; } = new List<AppUser>();
|
||||
|
||||
public List<Ticket> Tickets { get; set; } = new List<Ticket>();
|
||||
|
||||
public List<Activity> Activities { get; set; } = new List<Activity>();
|
||||
|
||||
public List<File> Files { get; set; } = new List<File>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TicketManager.Models
|
||||
{
|
||||
public class History
|
||||
public class Activity
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
|
@ -38,12 +38,11 @@ namespace TicketManager.Models
|
|||
|
||||
// [Display(Name = "Avatar")]
|
||||
// public byte[] Picture { get; set; }
|
||||
// public Role Role { get; set; }
|
||||
|
||||
public List<Assignment> Assignments { get; set; } = new List<Assignment>();
|
||||
|
||||
[Display(Name = "Activity")]
|
||||
public List<History> Edits { get; set; } = new List<History>();
|
||||
public List<Activity> Activities { get; set; } = new List<Activity>();
|
||||
|
||||
// Methods
|
||||
public List<Project> GetProjects()
|
||||
|
|
@ -53,7 +52,7 @@ namespace TicketManager.Models
|
|||
|
||||
public List<Ticket> GetTickets()
|
||||
{
|
||||
List<Ticket> tickets = new List<Ticket>();
|
||||
var tickets = new List<Ticket>();
|
||||
GetProjects().ForEach(p => tickets.Concat(p.Tickets));
|
||||
return tickets;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ namespace TicketManager.Models
|
|||
{
|
||||
public class File
|
||||
{
|
||||
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public string FileName { get; set; }
|
||||
|
|
|
|||
|
|
@ -11,18 +11,18 @@ namespace TicketManager.Models
|
|||
string Description { get; set; }
|
||||
DateTime CreatedAt { get; }
|
||||
DateTime PlannedEnding { get; set; }
|
||||
List<History> Edits { get; set; }
|
||||
List<Activity> Activities { get; set; }
|
||||
|
||||
public virtual void AddLogEntry(string description)//, User user)
|
||||
{
|
||||
History Edit = new History()
|
||||
Activity Activity = new Activity()
|
||||
{
|
||||
Description = description,
|
||||
ActivityType = ActivityType.Undefined,
|
||||
// User = user,
|
||||
UpdateDate = DateTime.Now
|
||||
};
|
||||
Edits.Add(Edit);
|
||||
Activities.Add(Activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ namespace TicketManager.Models
|
|||
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
|
||||
public DateTime PlannedEnding { get; set; }
|
||||
|
||||
// private decimal _progression;
|
||||
[Display(Name = "Progress")]
|
||||
public decimal Progression
|
||||
{
|
||||
|
|
@ -67,7 +66,7 @@ namespace TicketManager.Models
|
|||
|
||||
public List<Ticket> Tickets { get; set; } = new List<Ticket>();
|
||||
|
||||
public List<History> Edits { get; set; } = new List<History>();
|
||||
public List<Activity> Activities { get; set; } = new List<Activity>();
|
||||
|
||||
public List<File> Files { get; set; } = new List<File>();
|
||||
|
||||
|
|
@ -119,8 +118,7 @@ namespace TicketManager.Models
|
|||
this.AddMembers(projectMembers);
|
||||
}
|
||||
}
|
||||
// public int GetMembersCount() => this.GetMembers().Count();
|
||||
// public void GetTicketsCount() => this.Tickets.Count();
|
||||
|
||||
public void GetTicketsUpdates()
|
||||
{ throw new NotImplementedException("Not Implemented"); }
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace TicketManager.Models
|
|||
public Difficulty Difficulty { get; set; } = Difficulty.Undefined;
|
||||
public Category Category { get; set; } = Category.Undefined;
|
||||
|
||||
[Display(Name = "Created By")]
|
||||
// [Display(Name = "Created By")]
|
||||
// public AppUser Creator { get; set; }
|
||||
public Guid CreatorId { get; set; }
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ namespace TicketManager.Models
|
|||
// public int ProjectId { get; set; }
|
||||
public List<Note> Notes = new List<Note>();
|
||||
|
||||
public List<History> Edits = new List<History>();
|
||||
public List<Activity> Activities = new List<Activity>();
|
||||
|
||||
public List<File> Files = new List<File>();
|
||||
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -33,15 +33,17 @@
|
|||
|
||||
## TO DO
|
||||
|
||||
- Write API tests using Postman: request + test, environment variables, mock server
|
||||
- Annotate API request in controllers
|
||||
- Annotate Properties in Models
|
||||
- Write backend tests
|
||||
- Have a Look at typeahead component
|
||||
- Ensure Tickets Edits belong to Project Edits
|
||||
- Ensure Tickets Files belong to Project Files
|
||||
- Async model methods ?
|
||||
- setMembers & removeMembers from project api not working
|
||||
- Write a query class to refactor code and optimize perf on get queries (AsNoTracking)
|
||||
- repository + strategy to decouple controllers from DbContext. Easier testing
|
||||
- update assignments automatically from context
|
||||
- [ ] Write API tests using Postman: request + test, environment variables, mock server
|
||||
- [ ] Annotate API request in controllers
|
||||
- [ ] Annotate Properties in Models
|
||||
- [ ] Write backend tests
|
||||
- [ ] Have a Look at typeahead component
|
||||
- [ ] Ensure Tickets Edits belong to Project Edits
|
||||
- [ ] Ensure Tickets Files belong to Project Files
|
||||
- [ ] Async model methods ?
|
||||
- [ ] update assignments automatically from context
|
||||
- [ ] use PATCH instead of PUT
|
||||
- [ ] logging
|
||||
- [ ] check useRef, useReducer, dispatch
|
||||
- [ ] error page redirect when offline.
|
||||
- [ ] ticket/files/activities list placeholders when empty
|
||||
|
|
|
|||
1
Scripts/apiQueries.sh
Executable file
1
Scripts/apiQueries.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
curl --insecure https://localhost:5001/api/v1/
|
||||
1
Scripts/authentication.sh
Executable file
1
Scripts/authentication.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
rm -r Migrations
|
||||
rm app.db
|
||||
dotnet ef migrations add Migration1
|
||||
dotnet ef database update
|
||||
dotnet run
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
rm Controllers/AppUsersController.cs
|
||||
rm Controllers/TicketsController.cs
|
||||
rm Controllers/ProjectsController.cs
|
||||
dotnet aspnet-codegenerator controller -name AppUsersController -async -api -m AppUser -dc AppDbContext -outDir Controllers
|
||||
dotnet aspnet-codegenerator controller -name TicketsController -async -api -m Ticket -dc AppDbContext -outDir Controllers
|
||||
dotnet aspnet-codegenerator controller -name ProjectsController -async -api -m Project -dc AppDbContext -outDir Controllers
|
||||
43
Startup.cs
43
Startup.cs
|
|
@ -19,6 +19,7 @@ using TicketManager.Data;
|
|||
using TicketManager.Models;
|
||||
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
|
||||
[assembly: ApiController]
|
||||
namespace TicketManager
|
||||
|
|
@ -32,18 +33,33 @@ namespace TicketManager
|
|||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseSqlite(Configuration.GetConnectionString("Sqlite")));
|
||||
services.AddScoped<IProjectRepository, ProjectRepository>();
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson(options =>
|
||||
services.AddScoped<IAppUserRepository, AppUserRepository>();
|
||||
services.AddScoped<ITicketRepository, TicketRepository>();
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // avoid cycle ref errors
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = "https://dev-fyjrvohx.auth0.com/";
|
||||
options.Audience = "https://localhost:5001/api/V1/";
|
||||
//options.Authority = $"https://{Configuration["Auth0:Domain"]}/";
|
||||
//options.Audience = Configuration["Auth0:Audience"];
|
||||
});
|
||||
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson(options =>
|
||||
{
|
||||
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // avoid cycle ref errors
|
||||
}
|
||||
);
|
||||
|
||||
services.AddSpaStaticFiles(configuration =>
|
||||
{
|
||||
configuration.RootPath = "client/build";
|
||||
|
|
@ -70,17 +86,12 @@ namespace TicketManager
|
|||
services.AddSwaggerGenNewtonsoftSupport(); // explicit opt-in - needs to be placed after AddSwaggerGen()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
var repository = serviceProvider.GetRequiredService<IProjectRepository>();
|
||||
|
||||
// InitializeDatabaseAsync(repository).Wait()
|
||||
// var repository = serviceProvider.GetRequiredService<IProjectRepository>();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -90,7 +101,6 @@ namespace TicketManager
|
|||
app.UseHttpsRedirection();
|
||||
app.UseDefaultFiles();
|
||||
|
||||
|
||||
app.UseSwagger();
|
||||
|
||||
app.UseSwaggerUI(c =>
|
||||
|
|
@ -98,11 +108,9 @@ namespace TicketManager
|
|||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ticket Manager API v1");
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.UseSpaStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
|
|
@ -121,9 +129,4 @@ namespace TicketManager
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -3,10 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PropertyGroup >
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
|
@ -16,17 +13,18 @@
|
|||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="1.1.4" />
|
||||
<PackageReference Include="Moq" Version="4.13.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
|
||||
|
|
|
|||
|
|
@ -10,5 +10,9 @@
|
|||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=app.db"
|
||||
},
|
||||
"Auth0": {
|
||||
"Domain": "https://dev-fyjrvohx.auth0.com/",
|
||||
"Audience": "https://localhost:5001/api/V1/"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
.panel {
|
||||
padding-left: 0px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.field {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.city {
|
||||
display: flex;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(2, 0, 36, 1) 0%,
|
||||
rgba(25, 112, 245, 0.6399510487788865) 0%,
|
||||
rgba(0, 212, 255, 1) 100%
|
||||
);
|
||||
flex-direction: column;
|
||||
height: 40vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0px 20px 20px 20px;
|
||||
margin: 0px 0px 50px 0px;
|
||||
border: 1px solid;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px #888888;
|
||||
font-family: "Merriweather", serif;
|
||||
}
|
||||
.city h1 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
.city span {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.city .row {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.weatherError {
|
||||
color: #f16051;
|
||||
font-size: 20px;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
import React, { FC } from "react";
|
||||
import { AppRouter } from "./utils/router";
|
||||
import "./App.css";
|
||||
import Layout from "./pages/Layout";
|
||||
|
||||
const App: FC = () => {
|
||||
|
|
|
|||
38
client/src/VM/ProjectVM.ts
Normal file
38
client/src/VM/ProjectVM.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { Ticket } from "../types/Ticket";
|
||||
import { Project } from "../types/Project";
|
||||
import { AppFile } from "../types/AppFile";
|
||||
import { Activity } from "../types/Activity";
|
||||
import { User } from "../types/User";
|
||||
import { getRemainingdays } from "../utils/methods";
|
||||
|
||||
export default class ProjectVM {
|
||||
public id: number;
|
||||
public title: string;
|
||||
public description: string;
|
||||
public value: number;
|
||||
public tickets: Ticket[];
|
||||
public users: User[];
|
||||
public ticketsTotalCount: number;
|
||||
public ticketsDone: number;
|
||||
public remainingDays: number;
|
||||
public files: AppFile[];
|
||||
public activities: Activity[];
|
||||
|
||||
public constructor(project: Project) {
|
||||
this.id = project.id;
|
||||
this.title = project.title;
|
||||
this.description = project.description;
|
||||
this.users = project.users;
|
||||
this.value = project.progression;
|
||||
this.tickets = project.tickets;
|
||||
this.ticketsTotalCount =
|
||||
this.tickets === undefined ? 0 : this.tickets.length;
|
||||
this.ticketsDone =
|
||||
this.tickets === undefined
|
||||
? 0
|
||||
: this.tickets.filter(t => t.status === "Done").length;
|
||||
this.files = project.files;
|
||||
this.activities = project.activities;
|
||||
this.remainingDays = getRemainingdays(project.plannedEnding);
|
||||
}
|
||||
}
|
||||
49
client/src/components/ActivityCollection.tsx
Normal file
49
client/src/components/ActivityCollection.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import React, { FC } from "react";
|
||||
import { Activity } from "../types/Activity";
|
||||
|
||||
type IProps = {
|
||||
activities: Activity[];
|
||||
filterText: string;
|
||||
};
|
||||
|
||||
export const ActivityCollection: FC<IProps> = ({ activities, filterText }) => {
|
||||
return activities === undefined ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
<ul className="collection">
|
||||
{activities
|
||||
.filter(
|
||||
a =>
|
||||
a.description.toLowerCase().includes(filterText.toLowerCase()) ||
|
||||
a.user.firstName
|
||||
.toLowerCase()
|
||||
.includes(filterText.toLowerCase()) ||
|
||||
a.ticket.title.toLowerCase().includes(filterText.toLowerCase())
|
||||
)
|
||||
.map((activity: Activity) => (
|
||||
<li key={activity.id} className="collection-item avatar">
|
||||
<ActivityEntry activity={activity} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type IFProps = {
|
||||
activity: Activity;
|
||||
};
|
||||
|
||||
export const ActivityEntry: FC<IFProps> = ({ activity }) => {
|
||||
return (
|
||||
<>
|
||||
<img src={activity.user.picture} alt="" className="circle" />
|
||||
{/* <i className="material-icons circle">folder</i> */}
|
||||
<span className="title">
|
||||
{activity.user.firstName} {activity.description} {activity.ticket.title}
|
||||
</span>
|
||||
<p>{activity.date.toDateString()}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
34
client/src/components/ActivityList.tsx
Normal file
34
client/src/components/ActivityList.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React, { FC, useState, ChangeEvent, MouseEvent } from "react";
|
||||
import { ActivityCollection } from "./ActivityCollection";
|
||||
import { Activity } from "../types/Activity";
|
||||
import { FilterBar } from "./FilterBar";
|
||||
|
||||
type IProps = {
|
||||
activities: Activity[];
|
||||
};
|
||||
|
||||
export const ActivityList: FC<IProps> = ({ activities }) => {
|
||||
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>Activity</h3>
|
||||
<FilterBar
|
||||
filterText={filterText}
|
||||
handleChange={handleChange}
|
||||
clearFilterText={clearFilterText}
|
||||
/>
|
||||
</div>
|
||||
<ActivityCollection activities={activities} filterText={filterText} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
35
client/src/components/AppFileList.tsx
Normal file
35
client/src/components/AppFileList.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
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,15 +1,24 @@
|
|||
import React, { FC } from "react";
|
||||
import { FloatingButton } from "./FloatingButton";
|
||||
import { User } from "../types/User";
|
||||
|
||||
interface AvatarListProps {
|
||||
avatars: string[];
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export const AvatarList: FC<AvatarListProps> = ({ avatars }) => {
|
||||
return (
|
||||
export const AvatarList: FC<AvatarListProps> = ({ users }) => {
|
||||
return users === undefined ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
{avatars.map((avatar: string) => (
|
||||
<img className="circle" src={avatar} width="32vh" height="32vh" />
|
||||
{users.map((user: User, i: number) => (
|
||||
<img
|
||||
key={i}
|
||||
className="circle"
|
||||
src={user.picture}
|
||||
width="32vh"
|
||||
height="32vh"
|
||||
alt={user.fullName}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, Children } from "react";
|
||||
import React, { FC, MouseEvent } from "react";
|
||||
|
||||
interface IProps {
|
||||
icon?: string;
|
||||
|
|
@ -6,18 +6,20 @@ interface IProps {
|
|||
shape?: string;
|
||||
color?: string;
|
||||
text?: string;
|
||||
onClick?: (e: MouseEvent) => void;
|
||||
}
|
||||
|
||||
export const Button: FC<IProps> = ({
|
||||
size = "small",
|
||||
shape = "",
|
||||
color,
|
||||
text,
|
||||
onClick,
|
||||
children
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={`waves-effect waves-light btn-${size} ${shape} ${color}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
|
|
|||
45
client/src/components/FileCollection.tsx
Normal file
45
client/src/components/FileCollection.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import React, { FC } from "react";
|
||||
import { AppFile } from "../types/AppFile";
|
||||
|
||||
type IProps = {
|
||||
files: AppFile[];
|
||||
filterText: string;
|
||||
};
|
||||
|
||||
export const FileCollection: FC<IProps> = ({ files, filterText }) => {
|
||||
return (
|
||||
<>
|
||||
<ul className="collection">
|
||||
{files
|
||||
.filter(
|
||||
f =>
|
||||
f.name.toLowerCase().includes(filterText.toLowerCase()) ||
|
||||
f.format.toLowerCase().includes(filterText.toLowerCase())
|
||||
)
|
||||
.map((file: AppFile) => (
|
||||
<FileEntry file={file} key={file.id} />
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type IFProps = {
|
||||
file: AppFile;
|
||||
};
|
||||
|
||||
export const FileEntry: FC<IFProps> = ({ file }) => {
|
||||
return (
|
||||
<li className="collection-item avatar">
|
||||
{/* <img src={require("../images/user_1.jpg")} alt="" className="circle" /> */}
|
||||
<i className="material-icons circle">folder</i>
|
||||
<span className="title">{file.name}</span>
|
||||
<p>
|
||||
{file.size}kb {file.format}
|
||||
</p>
|
||||
<a href="#!" className="secondary-content">
|
||||
<i className="material-icons">more_vert</i>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
41
client/src/components/FilterBar.tsx
Normal file
41
client/src/components/FilterBar.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import React, { FC, ChangeEvent, MouseEvent } from "react";
|
||||
import { useRouteMatch } from "react-router-dom";
|
||||
|
||||
type IProps = {
|
||||
filterText: string;
|
||||
handleChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
clearFilterText: (e: MouseEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
export const FilterBar: FC<IProps> = ({
|
||||
filterText,
|
||||
handleChange,
|
||||
clearFilterText
|
||||
}) => {
|
||||
const { url } = useRouteMatch();
|
||||
const placeholder: string = url.split("/")[3] || "users";
|
||||
return (
|
||||
<>
|
||||
<div className="nav-wrapper">
|
||||
<div className="input-field">
|
||||
<input
|
||||
// className="validate"
|
||||
id="filter"
|
||||
type="search"
|
||||
required
|
||||
name="filter"
|
||||
value={filterText}
|
||||
placeholder={`Filter ${placeholder}`}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<label className="label-icon" htmlFor="search">
|
||||
<i className="material-icons">filter_list</i>
|
||||
</label>
|
||||
<i className="material-icons" onClick={clearFilterText}>
|
||||
close
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
import React, { FC } from "react";
|
||||
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"
|
||||
color = "red",
|
||||
onClick
|
||||
}) => {
|
||||
const iconComponent = <i className="material-icons left">{icon}</i>;
|
||||
return (
|
||||
<Button color={color} size={size} shape="btn-floating">
|
||||
<Button color={color} size={size} shape="btn-floating" onClick={onClick}>
|
||||
{iconComponent}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,54 +1,44 @@
|
|||
import React, { FC, MouseEvent } from "react";
|
||||
import { AvatarList } from "./AvatarList";
|
||||
import { Link } from "react-router-dom";
|
||||
import { getRemainingdays } from "../utils/methods";
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
tasksTotalCount?: number;
|
||||
tasksDone?: number;
|
||||
remainingDays?: number;
|
||||
avatars: string[];
|
||||
remainingDays: string;
|
||||
validateTicket: (event: MouseEvent) => void;
|
||||
archiveTicket: (event: MouseEvent) => void;
|
||||
}
|
||||
|
||||
export const HorizontalCard: FC<IProps> = ({
|
||||
title,
|
||||
tasksDone,
|
||||
tasksTotalCount,
|
||||
remainingDays,
|
||||
avatars,
|
||||
archiveTicket,
|
||||
validateTicket
|
||||
}) => {
|
||||
return (
|
||||
<div className="col s12">
|
||||
<div className="card horizontal">
|
||||
<div className="card-stacked">
|
||||
<div className="card-content">
|
||||
<div className="row">
|
||||
<div className="card-title">
|
||||
<h6>{title}</h6>
|
||||
</div>
|
||||
<span>Due {remainingDays} days</span>
|
||||
{/* <AvatarList avatars={avatars} /> */}
|
||||
<div className="right">
|
||||
{/* <i className=" material-icons">playlist_add_check</i>
|
||||
<span>
|
||||
{" "}
|
||||
{tasksDone}/{tasksTotalCount}
|
||||
</span> */}
|
||||
|
||||
<a>
|
||||
<i className="material-icons" onClick={validateTicket}>
|
||||
check
|
||||
</i>
|
||||
</a>
|
||||
<a>
|
||||
<i className="material-icons" onClick={archiveTicket}>
|
||||
archive
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div className="card horizontal">
|
||||
<div className="card-stacked">
|
||||
<div className="card-content">
|
||||
<div className="row">
|
||||
<div className="card-title">
|
||||
<h6>
|
||||
<Link to="#">
|
||||
<b>{title}</b>
|
||||
</Link>
|
||||
</h6>
|
||||
</div>
|
||||
<span>Due {getRemainingdays(remainingDays)} days</span>
|
||||
<div className="right">
|
||||
<Link to="#">
|
||||
<i className="material-icons" onClick={validateTicket}>
|
||||
check
|
||||
</i>
|
||||
</Link>
|
||||
<Link to="#">
|
||||
<i className="material-icons" onClick={archiveTicket}>
|
||||
archive
|
||||
</i>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
29
client/src/components/InputFile.tsx
Normal file
29
client/src/components/InputFile.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React, { FC } from "react";
|
||||
|
||||
type IProps = {};
|
||||
|
||||
export const InputFile: FC<IProps> = () => {
|
||||
return (
|
||||
<>
|
||||
<form action="/upload">
|
||||
<div className="file-field input-field">
|
||||
<div className="btn">
|
||||
<i className="material-icons ">cloud_upload</i>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*"
|
||||
/>
|
||||
</div>
|
||||
<div className="file-path-wrapper">
|
||||
<input
|
||||
className="file-path validate"
|
||||
type="text"
|
||||
placeholder="Upload one or more files"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
24
client/src/components/Modal.tsx
Normal file
24
client/src/components/Modal.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React, { FC, useState, 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 className="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
className="modal-close waves-effect waves-green btn"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
55
client/src/components/Preloader.tsx
Normal file
55
client/src/components/Preloader.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React, { FC } from "react";
|
||||
|
||||
export const Preloader: FC = () => {
|
||||
return (
|
||||
<div className="preloader-wrapper big active">
|
||||
<div className="spinner-layer spinner-blue">
|
||||
<div className="circle-clipper left">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="gap-patch">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="circle-clipper right">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="spinner-layer spinner-red">
|
||||
<div className="circle-clipper left">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="gap-patch">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="circle-clipper right">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="spinner-layer spinner-yellow">
|
||||
<div className="circle-clipper left">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="gap-patch">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="circle-clipper right">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="spinner-layer spinner-green">
|
||||
<div className="circle-clipper left">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="gap-patch">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
<div className="circle-clipper right">
|
||||
<div className="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, HTMLAttributes, CSSProperties } from "react";
|
||||
import React, { FC, CSSProperties } from "react";
|
||||
|
||||
type ProgressBarProps = {
|
||||
value: number;
|
||||
|
|
|
|||
|
|
@ -1,52 +1,47 @@
|
|||
import React, { FC } from "react";
|
||||
import { TabRouterHeader } from "./TabRouterHeader";
|
||||
import { TicketList } from "./TicketList";
|
||||
import { FileList } from "./AppFileList";
|
||||
import { Ticket } from "../types/Ticket";
|
||||
import { Switch, Route, useRouteMatch, Redirect } from "react-router-dom";
|
||||
import { AppFile } from "../types/AppFile";
|
||||
import { Route, useRouteMatch, Redirect } from "react-router-dom";
|
||||
import { ActivityList } from "./ActivityList";
|
||||
import { Activity } from "../types/Activity";
|
||||
|
||||
interface IProps {
|
||||
tickets: Ticket[];
|
||||
tasksTotalCount?: number;
|
||||
tasksDone?: number;
|
||||
remainingDays?: number;
|
||||
avatars: string[];
|
||||
tabNames: string[];
|
||||
files: AppFile[];
|
||||
activities: Activity[];
|
||||
}
|
||||
|
||||
export const TabRouter: FC<IProps> = ({
|
||||
tickets,
|
||||
tasksDone,
|
||||
tasksTotalCount,
|
||||
remainingDays,
|
||||
avatars
|
||||
tabNames,
|
||||
files,
|
||||
activities
|
||||
}) => {
|
||||
const { url } = useRouteMatch();
|
||||
return (
|
||||
<>
|
||||
<Switch>
|
||||
<div className="row">
|
||||
<TabRouterHeader />
|
||||
<div className="row">
|
||||
<TabRouterHeader tabNames={tabNames} />
|
||||
|
||||
<Redirect from={url} to={`${url}/tickets`} />
|
||||
<Redirect from={url} to={`${url}/tickets`} />
|
||||
|
||||
<Route path={`${url}/tickets`}>
|
||||
<TicketList
|
||||
tickets={tickets}
|
||||
tasksDone={tasksDone}
|
||||
tasksTotalCount={tasksTotalCount}
|
||||
remainingDays={remainingDays}
|
||||
avatars={avatars}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={`${url}/tickets`}>
|
||||
<TicketList tickets={tickets} />
|
||||
</Route>
|
||||
|
||||
<Route path={`${url}/files`}>
|
||||
{/* <TicketList tickets={tickets} /> */}
|
||||
</Route>
|
||||
<Route path={`${url}/files`}>
|
||||
<FileList files={files} />
|
||||
</Route>
|
||||
|
||||
<Route path={`${url}/activity`}>
|
||||
{/* <TicketList tickets={tickets} /> */}
|
||||
</Route>
|
||||
</div>
|
||||
</Switch>
|
||||
<Route path={`${url}/activity`}>
|
||||
<ActivityList activities={activities} />
|
||||
</Route>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,50 @@
|
|||
import React, { FC, useState } from "react";
|
||||
import { Link, useRouteMatch } from "react-router-dom";
|
||||
|
||||
interface IProps {
|
||||
tabClass?: string;
|
||||
tabNames: string[];
|
||||
}
|
||||
|
||||
export const TabRouterHeader: FC<IProps> = ({
|
||||
tabClass = "tab col s4",
|
||||
tabNames
|
||||
}) => {
|
||||
const [isActive, setIsActive] = useState(0);
|
||||
const nTabs = tabNames.length;
|
||||
return (
|
||||
<>
|
||||
<ul className="tabs z-depth-1">
|
||||
{tabNames.map((name, i) => (
|
||||
<TabUnit
|
||||
key={i}
|
||||
text={name}
|
||||
value={i.toString()}
|
||||
tabClass={tabClass}
|
||||
isActive={isActive}
|
||||
setIsActive={setIsActive}
|
||||
nTabs={nTabs}
|
||||
/>
|
||||
))}
|
||||
<li
|
||||
className="indicator"
|
||||
style={{
|
||||
left: `${(isActive / nTabs) * 100}%`,
|
||||
right: `${(1 - (isActive + 1) / nTabs) * 100}%`
|
||||
}}
|
||||
></li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface TabUnitProps {
|
||||
tabClass: string;
|
||||
isActive: number;
|
||||
setIsActive: React.Dispatch<React.SetStateAction<number>>;
|
||||
text: string;
|
||||
value: string;
|
||||
nTabs: number;
|
||||
}
|
||||
|
||||
const TabUnit: FC<TabUnitProps> = ({
|
||||
|
|
@ -14,15 +52,23 @@ const TabUnit: FC<TabUnitProps> = ({
|
|||
isActive,
|
||||
setIsActive,
|
||||
text,
|
||||
value
|
||||
value,
|
||||
nTabs
|
||||
}) => {
|
||||
const { url } = useRouteMatch();
|
||||
return (
|
||||
<li className={tabClass} key={value}>
|
||||
<li
|
||||
className={tabClass}
|
||||
key={value}
|
||||
style={{
|
||||
left: `${(isActive / nTabs) * 100}%`,
|
||||
right: `${(1 - (isActive + 1) / nTabs) * 100}%`
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={`${url}/${text}`}
|
||||
id={value}
|
||||
className={isActive === parseInt(value) ? "active" : ""}
|
||||
className={isActive === parseInt(value) ? "active pink lighten-5" : ""}
|
||||
onClick={() => setIsActive(parseInt(value))}
|
||||
>
|
||||
{text}
|
||||
|
|
@ -30,50 +76,3 @@ const TabUnit: FC<TabUnitProps> = ({
|
|||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
tabClass?: string;
|
||||
}
|
||||
|
||||
export const TabRouterHeader: FC<IProps> = ({
|
||||
tabClass = "tab col s3",
|
||||
|
||||
children
|
||||
}) => {
|
||||
const [isActive, setIsActive] = useState(1);
|
||||
|
||||
// const switchTab = (e: React.MouseEvent<HTMLAnchorElement>): void => {
|
||||
// e.preventDefault();
|
||||
// setIsActive(e.target.id);
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row col s12">
|
||||
<ul className="tabs">
|
||||
<TabUnit
|
||||
text="Tickets"
|
||||
value="1"
|
||||
tabClass={tabClass}
|
||||
isActive={isActive}
|
||||
setIsActive={setIsActive}
|
||||
/>
|
||||
<TabUnit
|
||||
text="Files"
|
||||
value="2"
|
||||
tabClass={tabClass}
|
||||
isActive={isActive}
|
||||
setIsActive={setIsActive}
|
||||
/>
|
||||
<TabUnit
|
||||
text="Activity"
|
||||
value="3"
|
||||
tabClass={tabClass}
|
||||
isActive={isActive}
|
||||
setIsActive={setIsActive}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,52 +1,62 @@
|
|||
import React, { FC } from "react";
|
||||
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";
|
||||
|
||||
type TicketListProps = {
|
||||
tickets: Ticket[];
|
||||
tasksTotalCount?: number;
|
||||
tasksDone?: number;
|
||||
remainingDays?: number;
|
||||
avatars: string[];
|
||||
};
|
||||
|
||||
export const TicketList: FC<TicketListProps> = ({
|
||||
tickets,
|
||||
tasksDone,
|
||||
tasksTotalCount,
|
||||
remainingDays,
|
||||
avatars
|
||||
}) => {
|
||||
export const TicketList: FC<TicketListProps> = ({ tickets }) => {
|
||||
const [filterText, setFilterText] = useState<string>("");
|
||||
const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||
setFilterText("");
|
||||
};
|
||||
const archiveTicket = () => {};
|
||||
const validateTicket = () => {};
|
||||
const onClick: (e: MouseEvent) => void = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
||||
e: ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setFilterText(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="col s12">
|
||||
<>
|
||||
<div className="row valign-wrapper">
|
||||
<div className="col s6 m4">
|
||||
<h2>Tickets</h2>
|
||||
</div>
|
||||
<div className="col s6 m8">
|
||||
<FloatingButton color="grey" size="big" />
|
||||
</div>
|
||||
<h3>Tickets</h3>
|
||||
<FloatingButton
|
||||
color=" blue-grey lighten-4"
|
||||
size="small"
|
||||
onClick={onClick}
|
||||
/>
|
||||
<FilterBar
|
||||
filterText={filterText}
|
||||
handleChange={handleChange}
|
||||
clearFilterText={clearFilterText}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{tickets.map((t: Ticket) => (
|
||||
<li key={t.id}>
|
||||
<HorizontalCard
|
||||
title={t.title}
|
||||
tasksDone={tasksDone}
|
||||
tasksTotalCount={tasksTotalCount}
|
||||
remainingDays={remainingDays}
|
||||
avatars={avatars}
|
||||
validateTicket={validateTicket}
|
||||
archiveTicket={archiveTicket}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col s12 grey">
|
||||
<ul>
|
||||
{tickets
|
||||
.filter(t =>
|
||||
t.title.toLowerCase().includes(filterText.toLowerCase())
|
||||
)
|
||||
.map((t: Ticket) => (
|
||||
<li key={t.id}>
|
||||
<HorizontalCard
|
||||
title={t.title}
|
||||
remainingDays={t.plannedEnding}
|
||||
validateTicket={validateTicket}
|
||||
archiveTicket={archiveTicket}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
103
client/src/components/UsersModal.tsx
Normal file
103
client/src/components/UsersModal.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import React, { FC, useState, ChangeEvent, useEffect } from "react";
|
||||
import { Modal } from "./Modal";
|
||||
import { AvatarList } from "./AvatarList";
|
||||
import { User } from "../types/User";
|
||||
import { FilterBar } from "./FilterBar";
|
||||
import { HttpResponse } from "../types/HttpResponse";
|
||||
import { get } from "../utils/http";
|
||||
import { Constants } from "../utils/Constants";
|
||||
|
||||
interface IProps {
|
||||
show: boolean;
|
||||
handleClose: () => void;
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export const UsersModal: FC<IProps> = ({ show, handleClose, users }) => {
|
||||
const [filterText, setFilterText] = useState<string>("");
|
||||
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
||||
e: ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setFilterText(e.target.value);
|
||||
};
|
||||
const [allUsers, setAllUsers] = useState();
|
||||
|
||||
async function httpGet(): Promise<void> {
|
||||
try {
|
||||
const response: HttpResponse<User> = await get<User>(
|
||||
`${Constants.usersURI}`
|
||||
);
|
||||
if (response.parsedBody !== undefined) {
|
||||
setAllUsers(response.parsedBody);
|
||||
// setIsLoading(false);
|
||||
}
|
||||
} catch (ex) {
|
||||
// setHasError(true);
|
||||
// setError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// if (id !== undefined) {
|
||||
httpGet();
|
||||
// } else {
|
||||
// setHasError(true);
|
||||
// setError("Bad Request");
|
||||
// }
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal show={show} handleClose={handleClose}>
|
||||
<div className="row valign-wrapper blue">
|
||||
<div className="col s10">
|
||||
<h4 className="white-text">Manage users</h4>
|
||||
</div>
|
||||
<div className="col s2">
|
||||
<i
|
||||
className="right material-icons blue lighten-3 circle"
|
||||
onClick={handleClose}
|
||||
>
|
||||
close
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div className="center">
|
||||
<AvatarList users={users} />
|
||||
<FilterBar
|
||||
filterText={filterText}
|
||||
clearFilterText={() => setFilterText("")}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="code">{allUsers}</div> */}
|
||||
<form>
|
||||
<ul>
|
||||
{users.map((u: User) => (
|
||||
<li key={u.id}>
|
||||
<div className="row">
|
||||
<input
|
||||
id={u.id}
|
||||
type="checkbox"
|
||||
name="active"
|
||||
value="true"
|
||||
onChange={() => false}
|
||||
// checked
|
||||
/>
|
||||
<span>
|
||||
{u.fullName}
|
||||
<img
|
||||
className="circle"
|
||||
src={u.picture}
|
||||
width="32vh"
|
||||
height="32vh"
|
||||
alt={u.fullName}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
19
client/src/controllers/ErrorController.tsx
Normal file
19
client/src/controllers/ErrorController.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React, { FC } from "react";
|
||||
import { Redirect } from "react-router-dom";
|
||||
|
||||
interface IProps {
|
||||
error: any;
|
||||
}
|
||||
|
||||
export const ErrorController: FC<IProps> = ({ error }) => {
|
||||
switch (error) {
|
||||
case "Bad Request":
|
||||
return <Redirect to="/400" />;
|
||||
|
||||
case "Not Found":
|
||||
return <Redirect to="/404" />;
|
||||
|
||||
default:
|
||||
return <Redirect to="/404" />;
|
||||
}
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
|
|
|
|||
10
client/src/pages/NotFoundPage.tsx
Normal file
10
client/src/pages/NotFoundPage.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import React, { FC } from "react";
|
||||
|
||||
interface IProps {}
|
||||
export const NotFoundPage: FC<IProps> = () => {
|
||||
return (
|
||||
<div className="section">
|
||||
<p>error</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,32 +1,50 @@
|
|||
import React, { FC } from "react";
|
||||
import React, { FC, useState } from "react";
|
||||
import ProjectVM from "../VM/ProjectVM";
|
||||
import { Header } from "../components/Header";
|
||||
import { AvatarList } from "../components/AvatarList";
|
||||
import { ProgressBar } from "../components/ProgressBar";
|
||||
import ProjectVM from "../viewModels/ProjectVM";
|
||||
import { TabRouter } from "../components/TabRouter";
|
||||
import { FloatingButton } from "../components/FloatingButton";
|
||||
import { UsersModal } from "../components/UsersModal";
|
||||
|
||||
interface IProps {
|
||||
viewModel: ProjectVM;
|
||||
}
|
||||
|
||||
export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
avatars,
|
||||
users,
|
||||
value,
|
||||
tickets,
|
||||
ticketsDone,
|
||||
ticketsTotalCount,
|
||||
remainingDays
|
||||
remainingDays,
|
||||
files,
|
||||
activities
|
||||
} = viewModel;
|
||||
|
||||
const tabNames: string[] = ["Tickets", "Files", "Activity"];
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="section">
|
||||
<div className="container">
|
||||
<Header title={title} description={description} />
|
||||
<div className="row valign-wrapper">
|
||||
<AvatarList avatars={avatars} />
|
||||
<FloatingButton icon="add" color="grey" size="small" />
|
||||
<AvatarList users={users} />
|
||||
<FloatingButton
|
||||
icon="add"
|
||||
color="grey"
|
||||
size="small"
|
||||
onClick={() => setShowModal(true)}
|
||||
/>
|
||||
<UsersModal
|
||||
show={showModal}
|
||||
users={users}
|
||||
handleClose={() => setShowModal(false)}
|
||||
/>
|
||||
</div>
|
||||
<ProgressBar
|
||||
value={value}
|
||||
|
|
@ -35,11 +53,10 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
|||
remainingDays={remainingDays}
|
||||
/>
|
||||
<TabRouter
|
||||
tabNames={tabNames}
|
||||
tickets={tickets}
|
||||
tasksDone={ticketsDone}
|
||||
tasksTotalCount={ticketsTotalCount}
|
||||
remainingDays={remainingDays}
|
||||
avatars={avatars}
|
||||
files={files}
|
||||
activities={activities}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export const TicketPage: FC = () => {
|
|||
description="Research, ideate and present brand concepts for client consideration"
|
||||
title="Brand Concept and Design"
|
||||
/>
|
||||
<AvatarList avatars={["../images/user_1.jpg", "../images/user_2.jpg"]} />
|
||||
{/* <AvatarList users={["../images/user_1.jpg", "../images/user_2.jpg"]} /> */}
|
||||
<ProgressBar value={60} />
|
||||
{/* // <TabView>
|
||||
// <ChildTicket/>
|
||||
|
|
|
|||
1
client/src/react-app-env.d.ts
vendored
1
client/src/react-app-env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="react-scripts" />
|
||||
10
client/src/types/Activity.ts
Normal file
10
client/src/types/Activity.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { User } from "./User";
|
||||
import { Ticket } from "./Ticket";
|
||||
|
||||
export interface Activity {
|
||||
id: number;
|
||||
description: string;
|
||||
date: Date;
|
||||
user: User;
|
||||
ticket: Ticket;
|
||||
}
|
||||
9
client/src/types/AppFile.ts
Normal file
9
client/src/types/AppFile.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { User } from "./User";
|
||||
|
||||
export interface AppFile {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
format: string;
|
||||
size: number;
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export interface File {
|
||||
Id: number;
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export interface History {
|
||||
Id: number;
|
||||
}
|
||||
3
client/src/types/HttpResponse.ts
Normal file
3
client/src/types/HttpResponse.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface HttpResponse<T> extends Response {
|
||||
parsedBody?: T;
|
||||
}
|
||||
|
|
@ -1,12 +1,19 @@
|
|||
import { Ticket } from "./Ticket";
|
||||
import { User } from "./User";
|
||||
import { AppFile } from "./AppFile";
|
||||
import { Activity } from "./Activity";
|
||||
|
||||
export interface Project {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
progression: number;
|
||||
tickets: Ticket[];
|
||||
users: User[];
|
||||
createdAt: string;
|
||||
plannedEnding: string;
|
||||
progression: number;
|
||||
status: string;
|
||||
manager: User;
|
||||
users: User[];
|
||||
tickets: Ticket[];
|
||||
files: AppFile[];
|
||||
activities: Activity[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export interface Ticket {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
status: string;
|
||||
plannedEnding: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
export interface User {
|
||||
id: string;
|
||||
picture: string;
|
||||
firstName: string;
|
||||
fullName?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export class Constants {
|
||||
static getProjectURI: string = "/api/projects";
|
||||
static projectsURI: string = "/api/v1/projects";
|
||||
static ticketsURI: string = "/api/v1/tickets";
|
||||
static usersURI: string = "/api/v1/users";
|
||||
}
|
||||
|
|
|
|||
35
client/src/utils/http.ts
Normal file
35
client/src/utils/http.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { HttpResponse } from "../types/HttpResponse";
|
||||
|
||||
export async function http<T>(request: RequestInfo): Promise<HttpResponse<T>> {
|
||||
const response: HttpResponse<T> = await fetch(request);
|
||||
try {
|
||||
response.parsedBody = await response.json();
|
||||
} catch (ex) {}
|
||||
if (!response.ok) {
|
||||
throw response.statusText;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function get<T>(
|
||||
path: string,
|
||||
args: RequestInit = { method: "get" }
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await http<T>(new Request(path, args));
|
||||
}
|
||||
|
||||
export async function post<T>(
|
||||
path: string,
|
||||
body: any,
|
||||
args: RequestInit = { method: "post", body: JSON.stringify(body) }
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await http<T>(new Request(path, args));
|
||||
}
|
||||
|
||||
export async function put<T>(
|
||||
path: string,
|
||||
body: any,
|
||||
args: RequestInit = { method: "put", body: JSON.stringify(body) }
|
||||
): Promise<HttpResponse<T>> {
|
||||
return await http<T>(new Request(path, args));
|
||||
}
|
||||
7
client/src/utils/methods.ts
Normal file
7
client/src/utils/methods.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export const getRemainingdays: (endDate: string) => number = (
|
||||
endDate: string
|
||||
) => {
|
||||
let endingDate: Date = new Date(endDate);
|
||||
let today: Date = new Date();
|
||||
return Math.abs(endingDate.getDate() - today.getDate());
|
||||
};
|
||||
|
|
@ -1,19 +1,31 @@
|
|||
import React from "react";
|
||||
import { Router, Route, Switch, Link, NavLink } from "react-router-dom";
|
||||
import {
|
||||
Router,
|
||||
Route,
|
||||
Switch
|
||||
// Redirect
|
||||
//Link, NavLink
|
||||
} from "react-router-dom";
|
||||
import * as creacteHistory from "history";
|
||||
import { TicketPage } from "../pages/TicketPage";
|
||||
import { HomeController } from "../controllers/HomeController";
|
||||
// import { TicketPage } from "../pages/TicketPage";
|
||||
// import { HomeController } from "../controllers/HomeController";
|
||||
import { ProjectController } from "../controllers/ProjectController";
|
||||
import { UserController } from "../controllers/UserController";
|
||||
import { TicketController } from "../controllers/TicketController";
|
||||
import { NotFoundPage } from "../pages/NotFoundPage";
|
||||
import { TestPage } from "../pages/TestPage";
|
||||
// import { UserController } from "../controllers/UserController";
|
||||
// import { TicketController } from "../controllers/TicketController";
|
||||
|
||||
export const history = creacteHistory.createBrowserHistory();
|
||||
|
||||
export const AppRouter = () => {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<div>
|
||||
<div className="grey lighten-4">
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<TestPage />
|
||||
</Route>
|
||||
|
||||
{/* <Route path="/">
|
||||
<HomeController />
|
||||
</Route>
|
||||
|
|
@ -26,6 +38,14 @@ export const AppRouter = () => {
|
|||
{/* <Route path="/tickets/:id">
|
||||
<TicketController />
|
||||
</Route> */}
|
||||
|
||||
<Route path="/404">
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
|
||||
{/* <Route path="*">
|
||||
<Redirect to="/error" />
|
||||
</Route> */}
|
||||
</Switch>
|
||||
</div>
|
||||
</Router>
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { Ticket } from "../types/Ticket";
|
||||
import { Project } from "../types/Project";
|
||||
import { Constants } from "../utils/Constants";
|
||||
import { User } from "../types/User";
|
||||
|
||||
export default class ProjectVM {
|
||||
public id: number;
|
||||
public title: string;
|
||||
public description: string;
|
||||
public value: number;
|
||||
public tickets: Ticket[];
|
||||
public avatars: string[];
|
||||
public ticketsTotalCount: number;
|
||||
public ticketsDone: number;
|
||||
public remainingDays: number;
|
||||
|
||||
/**
|
||||
* getMembers
|
||||
*/
|
||||
// public getMembers(): string {
|
||||
// let res: Promise<Response> = fetch(
|
||||
// `${Constants.getProjectURI}/${this.id}/members`
|
||||
// );
|
||||
// return JSON.stringify(res);
|
||||
// // res.json();
|
||||
// }
|
||||
|
||||
public constructor(project: Project) {
|
||||
this.id = project.id;
|
||||
this.title = project.title;
|
||||
this.description = project.description;
|
||||
this.avatars = project.users.map(u => u.picture);
|
||||
this.value = project.progression;
|
||||
this.tickets = project.tickets;
|
||||
this.ticketsTotalCount = this.tickets.length;
|
||||
this.ticketsDone = this.tickets.filter(t => t.status === "Done").length;
|
||||
|
||||
let endingDate: Date = new Date(project.plannedEnding);
|
||||
let today: Date = new Date();
|
||||
let plannedEnding: number = Math.abs(
|
||||
endingDate.getDate() - today.getDate()
|
||||
);
|
||||
|
||||
this.remainingDays = plannedEnding;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue