15d7edec8f
This commit introduces a new feature to display meeting history for teams. It adds a `TeamMeetingHistoryDialog` component that shows the meeting history in a dialog format, including a loading state and error handling. Additionally, a `TeamMeetingHistoryBadge` component is created to display the count of meetings for each team, enhancing the user interface with tooltips for better interaction. The `Details` and `Index` components are updated to integrate these new features, allowing users to view meeting history directly from the team details and index pages. These changes improve the overall functionality and user experience in managing team meetings.
227 lines
7.8 KiB
C#
227 lines
7.8 KiB
C#
using Core.Entities;
|
|
using Core.Services;
|
|
using Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Security.Claims;
|
|
|
|
namespace WebApp.Services;
|
|
|
|
public class TeamMeetingHistoryService : ITeamMeetingHistoryService
|
|
{
|
|
private readonly AppDbContext _context;
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
private readonly ILogger<TeamMeetingHistoryService> _logger;
|
|
private readonly INotesService _notesService;
|
|
private readonly INoteNamingService _noteNamingService;
|
|
|
|
public TeamMeetingHistoryService(
|
|
AppDbContext context,
|
|
IHttpContextAccessor httpContextAccessor,
|
|
ILogger<TeamMeetingHistoryService> logger,
|
|
INotesService notesService,
|
|
INoteNamingService noteNamingService)
|
|
{
|
|
_context = context;
|
|
_httpContextAccessor = httpContextAccessor;
|
|
_logger = logger;
|
|
_notesService = notesService;
|
|
_noteNamingService = noteNamingService;
|
|
}
|
|
|
|
private string? GetCurrentUserEmail()
|
|
{
|
|
var user = _httpContextAccessor.HttpContext?.User;
|
|
if (user == null)
|
|
return null;
|
|
|
|
return user.FindFirstValue(ClaimTypes.Email) ?? user.Identity?.Name;
|
|
}
|
|
|
|
public async Task<IEnumerable<TeamMeetingHistory>> GetMeetingHistoriesAsync(DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
var query = _context.TeamMeetingHistories
|
|
.AsNoTracking()
|
|
.Include(tmh => tmh.Teams)
|
|
.ThenInclude(t => t.Event)
|
|
.Include(tmh => tmh.Students)
|
|
.AsQueryable();
|
|
|
|
if (startDate.HasValue)
|
|
{
|
|
var start = startDate.Value.Date;
|
|
query = query.Where(tmh => tmh.MeetingDate >= start);
|
|
}
|
|
|
|
if (endDate.HasValue)
|
|
{
|
|
var end = endDate.Value.Date.AddDays(1); // Include the entire end date
|
|
query = query.Where(tmh => tmh.MeetingDate < end);
|
|
}
|
|
|
|
return await query
|
|
.OrderByDescending(tmh => tmh.MeetingDate)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<TeamMeetingHistory?> GetMeetingHistoryAsync(int id)
|
|
{
|
|
return await _context.TeamMeetingHistories
|
|
.Include(tmh => tmh.Teams)
|
|
.ThenInclude(t => t.Event)
|
|
.Include(tmh => tmh.Teams)
|
|
.ThenInclude(t => t.Students)
|
|
.Include(tmh => tmh.Students)
|
|
.FirstOrDefaultAsync(tmh => tmh.Id == id);
|
|
}
|
|
|
|
public async Task<TeamMeetingHistory> CreateMeetingHistoryAsync(TeamMeetingHistory meetingHistory)
|
|
{
|
|
// Create a new meeting history entity to avoid tracking conflicts
|
|
var newMeetingHistory = new TeamMeetingHistory
|
|
{
|
|
MeetingDate = meetingHistory.MeetingDate.Date // Normalize to date only
|
|
};
|
|
|
|
// Attach teams by loading them from the database to avoid tracking conflicts
|
|
var teamIds = meetingHistory.Teams.Select(t => t.Id).ToList();
|
|
var teams = await _context.Teams
|
|
.Where(t => teamIds.Contains(t.Id))
|
|
.ToListAsync();
|
|
|
|
// Attach students by loading them from the database to avoid tracking conflicts
|
|
var studentIds = meetingHistory.Students.Select(s => s.Id).ToList();
|
|
var students = await _context.Students
|
|
.Where(s => studentIds.Contains(s.Id))
|
|
.ToListAsync();
|
|
|
|
// Attach teams and students to the new meeting history
|
|
newMeetingHistory.Teams = teams;
|
|
newMeetingHistory.Students = students;
|
|
|
|
_context.TeamMeetingHistories.Add(newMeetingHistory);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Meeting history created: {MeetingHistoryId} for date {MeetingDate}",
|
|
newMeetingHistory.Id, newMeetingHistory.MeetingDate);
|
|
|
|
return newMeetingHistory;
|
|
}
|
|
|
|
public async Task<TeamMeetingHistory> UpdateMeetingHistoryAsync(TeamMeetingHistory meetingHistory)
|
|
{
|
|
var existingHistory = await _context.TeamMeetingHistories
|
|
.Include(tmh => tmh.Teams)
|
|
.Include(tmh => tmh.Students)
|
|
.FirstOrDefaultAsync(tmh => tmh.Id == meetingHistory.Id);
|
|
|
|
if (existingHistory == null)
|
|
{
|
|
throw new InvalidOperationException($"Meeting history with ID {meetingHistory.Id} not found.");
|
|
}
|
|
|
|
// Update properties
|
|
existingHistory.MeetingDate = meetingHistory.MeetingDate.Date; // Normalize to date only
|
|
|
|
// Update teams
|
|
existingHistory.Teams.Clear();
|
|
foreach (var team in meetingHistory.Teams)
|
|
{
|
|
var existingTeam = await _context.Teams.FindAsync(team.Id);
|
|
if (existingTeam != null)
|
|
{
|
|
existingHistory.Teams.Add(existingTeam);
|
|
}
|
|
}
|
|
|
|
// Update students
|
|
existingHistory.Students.Clear();
|
|
foreach (var student in meetingHistory.Students)
|
|
{
|
|
var existingStudent = await _context.Students.FindAsync(student.Id);
|
|
if (existingStudent != null)
|
|
{
|
|
existingHistory.Students.Add(existingStudent);
|
|
}
|
|
}
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Meeting history updated: {MeetingHistoryId} by {User}",
|
|
meetingHistory.Id, GetCurrentUserEmail());
|
|
|
|
return existingHistory;
|
|
}
|
|
|
|
public async Task DeleteMeetingHistoryAsync(int id)
|
|
{
|
|
var meetingHistory = await _context.TeamMeetingHistories
|
|
.FirstOrDefaultAsync(tmh => tmh.Id == id);
|
|
|
|
if (meetingHistory == null)
|
|
{
|
|
throw new InvalidOperationException($"Meeting history with ID {id} not found.");
|
|
}
|
|
|
|
_context.TeamMeetingHistories.Remove(meetingHistory);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Meeting history deleted: {MeetingHistoryId}", id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the meeting note for a specific meeting date by looking it up by title.
|
|
/// </summary>
|
|
/// <param name="meetingDate">The date of the meeting</param>
|
|
/// <returns>The note if found, null otherwise</returns>
|
|
public async Task<Note?> GetMeetingNoteAsync(DateTime meetingDate)
|
|
{
|
|
var noteTitle = _noteNamingService.GetMeetingNoteTitle(meetingDate);
|
|
var notes = await _notesService.GetNotesAsync(includeDeleted: false);
|
|
return notes.FirstOrDefault(n => n.Title == noteTitle);
|
|
}
|
|
|
|
public async Task<IEnumerable<Team>> GetTeamsForDateAsync(DateTime date)
|
|
{
|
|
var normalizedDate = date.Date;
|
|
return await _context.TeamMeetingHistories
|
|
.AsNoTracking()
|
|
.Where(tmh => tmh.MeetingDate == normalizedDate)
|
|
.SelectMany(tmh => tmh.Teams)
|
|
.Include(t => t.Event)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<IEnumerable<Student>> GetStudentsForDateAsync(DateTime date)
|
|
{
|
|
var normalizedDate = date.Date;
|
|
return await _context.TeamMeetingHistories
|
|
.AsNoTracking()
|
|
.Where(tmh => tmh.MeetingDate == normalizedDate)
|
|
.SelectMany(tmh => tmh.Students)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<IEnumerable<TeamMeetingHistory>> GetMeetingHistoriesForTeamAsync(int teamId)
|
|
{
|
|
return await _context.TeamMeetingHistories
|
|
.AsNoTracking()
|
|
.Include(tmh => tmh.Teams)
|
|
.ThenInclude(t => t.Event)
|
|
.Include(tmh => tmh.Students)
|
|
.Where(tmh => tmh.Teams.Any(t => t.Id == teamId))
|
|
.OrderByDescending(tmh => tmh.MeetingDate)
|
|
.ThenByDescending(tmh => tmh.Id)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<int> GetMeetingHistoryCountForTeamAsync(int teamId)
|
|
{
|
|
return await _context.TeamMeetingHistories
|
|
.AsNoTracking()
|
|
.Where(tmh => tmh.Teams.Any(t => t.Id == teamId))
|
|
.CountAsync();
|
|
}
|
|
}
|