Updated for DawnTrail
@ -0,0 +1,301 @@
|
||||
@page "/{recordType}/{territoryId}"
|
||||
|
||||
@model Expedience.Web.Pages.RecordsModel
|
||||
@{
|
||||
ViewData["Title"] = "Expedience";
|
||||
|
||||
var roleOrder = new Dictionary<string, int>
|
||||
{
|
||||
{"Tank", 0},
|
||||
{"Healer", 1},
|
||||
{"DPS", 2}
|
||||
};
|
||||
|
||||
var jobRoles = new Dictionary<string, string>
|
||||
{
|
||||
{"MRD", "Tank"}, {"GLA", "Tank"}, {"PLD", "Tank"}, {"GNB", "Tank"}, {"DRK", "Tank"}, {"WAR", "Tank"},
|
||||
{"CNJ", "Healer"}, {"SCH", "Healer"}, {"SGE", "Healer"}, {"WHM", "Healer"}, {"AST", "Healer"}
|
||||
};
|
||||
|
||||
Func<string, string> getRole = job => jobRoles.ContainsKey(job) ? jobRoles[job] : "DPS";
|
||||
|
||||
<style>
|
||||
#topTenResultsTable {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
#topTenResultsTable th,
|
||||
#topTenResultsTable td {
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#topTenResultsTable .expand-column {
|
||||
width: 3%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#topTenResultsTable .rank-column {
|
||||
width: 7%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#topTenResultsTable .player-column {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#topTenResultsTable .job-column {
|
||||
width: 7%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#topTenResultsTable .date-column {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
#topTenResultsTable .world-column {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
#topTenResultsTable .duration-column {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
#topTenResultsTable .rank-column,
|
||||
#topTenResultsTable .job-column {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.expand-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.duty-members-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.duty-members-table td {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.duty-members-table .member-icon {
|
||||
width: 32px;
|
||||
padding: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.duty-members-table img {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.duty-members-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.duty-details {
|
||||
font-size: 0.9em;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.duty-details p {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.duty-details strong {
|
||||
font-weight: bold;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.duty-info-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.duty-members-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.duty-members-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 1px;
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.player-icon {
|
||||
box-shadow: 0 0 5px 3px rgba(255, 215, 0, 0.7);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.duty-details {
|
||||
font-size: 0.9em;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-weight: bold;
|
||||
color: #aaa;
|
||||
width: 100px; /* Adjust this value based on your longest label */
|
||||
padding-right: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#topTenResultsTable td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.duty-header {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
color: #fff; /* Adjust color as needed */
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
var dutyName = Model.Results.FirstOrDefault()?.Territory?.ContentName ?? "Unknown Duty";
|
||||
|
||||
<h2 class="duty-header">@dutyName - @Model.Mode</h2>
|
||||
|
||||
|
||||
<table class="table table-dark table-striped" id="topTenResultsTable" data-toggle="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="expand-column"></th>
|
||||
<th class="rank-column" data-sortable="true">Rank</th>
|
||||
<th class="player-column" data-sortable="true">Player</th>
|
||||
<th class="job-column" data-sortable="true">Job</th>
|
||||
<th class="duration-column" data-sortable="true" data-sorter="durationSorter">Duration</th>
|
||||
<th class="date-column" data-sortable="true">Date</th>
|
||||
<th class="world-column" data-sortable="true">Data Center</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int i = 0; i < Math.Min(Model.Results.Count, 10); i++)
|
||||
{
|
||||
var result = Model.Results[i];
|
||||
var playerMember = result.DutyMembers.FirstOrDefault(m => m.IsPlayer == true);
|
||||
<tr>
|
||||
<td>
|
||||
<button class="expand-button" onclick="toggleDutyMembers(this, '@i')">+</button>
|
||||
</td>
|
||||
<td class="rank-column">@(i + 1)</td>
|
||||
<td class="player-column">@result.User.UserName</td>
|
||||
<td class="job-column">
|
||||
@if (playerMember != null)
|
||||
{
|
||||
<img src="~/images/@(playerMember.ClassJob.ToLower()).png" alt="@playerMember.ClassJob" title="@playerMember.ClassJob" width="24" height="24" />
|
||||
}
|
||||
</td>
|
||||
<td class="duration-column">@($"{result.Hours:D2}:{result.Minutes:D2}:{result.Seconds:D2}.{result.Milliseconds:D3}")</td>
|
||||
<td class="date-column">@result.EndTime.ToString("yyyy-MM-dd")</td>
|
||||
<td class="world-column">@result.DataCenter</td>
|
||||
</tr>
|
||||
<tr class="hidden" id="dutyMembers-@i">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="duty-members-container">
|
||||
@foreach (var group in result.DutyMembers.GroupBy(m => m.GroupNumber).OrderBy(g => g.Key))
|
||||
{
|
||||
<div class="duty-members-group">
|
||||
@foreach (var member in group
|
||||
.OrderBy(m => roleOrder[getRole(m.ClassJob)])
|
||||
.ThenBy(m => m.ClassJob))
|
||||
{
|
||||
<img class="member-icon @(member.IsPlayer ? "player-icon" : "")"
|
||||
src="~/images/@(member.ClassJob).png"
|
||||
alt="@member.ClassJob"
|
||||
title="@member.ClassJob" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="4">
|
||||
<div class="duty-details">
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">Start Time:</div>
|
||||
<div class="detail-value">@result.StartTime.ToString("yyyy-MM-dd HH:mm:ss")</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">End Time:</div>
|
||||
<div class="detail-value">@result.EndTime.ToString("yyyy-MM-dd HH:mm:ss")</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">Uploaded At:</div>
|
||||
<div class="detail-value">@result.UploadedAt.ToString("yyyy-MM-dd HH:mm:ss")</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">Game Version:</div>
|
||||
<div class="detail-value">@result.GameVersion</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-label">Plugin Version:</div>
|
||||
<div class="detail-value">@result.PluginVersion</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
<a href="javascript:history.back()">Go Back</a>
|
||||
|
||||
<script>
|
||||
function toggleDutyMembers(button, index) {
|
||||
var row = document.getElementById('dutyMembers-' + index);
|
||||
if (row.style.display === 'none' || row.style.display === '') {
|
||||
row.style.display = 'table-row';
|
||||
button.textContent = '-';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
button.textContent = '+';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Expedience.Infrastructure.Models;
|
||||
using Expedience.Web.Services;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Expedience.Web.Pages
|
||||
{
|
||||
public class RecordsModel(IApiService apiService) : PageModel
|
||||
{
|
||||
public List<DutyCompletionResult> Results { get; set; }
|
||||
public string Mode { get; set; }
|
||||
|
||||
public async Task OnGet(string recordType, int territoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
var recordTypeNum = 0;
|
||||
|
||||
if (recordType == "normal")
|
||||
{
|
||||
Mode = "Normal";
|
||||
recordTypeNum = 1;
|
||||
}
|
||||
else if (recordType == "solo")
|
||||
{
|
||||
Mode = "Solo";
|
||||
recordTypeNum = 2;
|
||||
}
|
||||
else if (recordType == "mine")
|
||||
{
|
||||
Mode = "MINE";
|
||||
recordTypeNum = 3;
|
||||
}
|
||||
else if (recordType == "npc")
|
||||
{
|
||||
Mode = "NPC Assisted";
|
||||
recordTypeNum = 4;
|
||||
}
|
||||
else
|
||||
throw new Exception($"Record Type {recordType} not found");
|
||||
|
||||
Results = await apiService.GetTopXRecords(recordTypeNum, territoryId, 10, cancellationToken);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |