create front end for Planner

This commit is contained in:
DESKTOP-T0O5CDB\DESK-555BD
2024-01-19 19:34:14 -07:00
parent bc2bb3636b
commit 60edb65b55
11 changed files with 348 additions and 58 deletions

View File

@@ -1188,6 +1188,12 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
{
//populate createdDate
if (planRecord.Id == default)
{
planRecord.DateCreated = DateTime.Now.ToString("G");
}
planRecord.DateModified = DateTime.Now.ToString("G");
//move files from temp.
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
@@ -1198,6 +1204,15 @@ namespace CarCareTracker.Controllers
{
return PartialView("_PlanRecordModal", new PlanRecordInput());
}
[HttpPost]
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress)
{
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
existingRecord.Progress = planProgress;
existingRecord.DateModified = DateTime.Now;
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
return Json(result);
}
[HttpGet]
public IActionResult GetPlanRecordForEditById(int planRecordId)
{
@@ -1207,12 +1222,12 @@ namespace CarCareTracker.Controllers
{
Id = result.Id,
Description = result.Description,
DateCreated = result.DateCreated.ToShortDateString(),
DateModified = result.DateModified.ToShortDateString(),
DateCreated = result.DateCreated.ToString("G"),
DateModified = result.DateModified.ToString("G"),
ImportMode = result.ImportMode,
Priority = result.Priority,
Progress = result.Progress,
Costs = result.Costs,
Cost = result.Cost,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files

View File

@@ -1,4 +1,4 @@
namespace CarCareTracker.Enum
namespace CarCareTracker.Models
{
public enum PlanPriority
{

View File

@@ -1,4 +1,4 @@
namespace CarCareTracker.Enum
namespace CarCareTracker.Models
{
public enum PlanProgress
{

View File

@@ -1,6 +1,4 @@
using CarCareTracker.Enum;
namespace CarCareTracker.Models
namespace CarCareTracker.Models
{
public class PlanRecord
{
@@ -14,6 +12,6 @@ namespace CarCareTracker.Models
public ImportMode ImportMode { get; set; }
public PlanPriority Priority { get; set; }
public PlanProgress Progress { get; set; }
public List<PlanCostItem> Costs { get; set; } = new List<PlanCostItem>();
public decimal Cost { get; set; }
}
}

View File

@@ -1,6 +1,4 @@
using CarCareTracker.Enum;
namespace CarCareTracker.Models
namespace CarCareTracker.Models
{
public class PlanRecordInput
{
@@ -14,7 +12,7 @@ namespace CarCareTracker.Models
public ImportMode ImportMode { get; set; }
public PlanPriority Priority { get; set; }
public PlanProgress Progress { get; set; }
public List<PlanCostItem> Costs { get; set; } = new List<PlanCostItem>();
public decimal Cost { get; set; }
public PlanRecord ToPlanRecord() { return new PlanRecord {
Id = Id,
VehicleId = VehicleId,
@@ -24,7 +22,7 @@ namespace CarCareTracker.Models
Notes = Notes,
Files = Files,
ImportMode = ImportMode,
Costs = Costs,
Cost = Cost,
Priority = Priority,
Progress = Progress
}; }

View File

@@ -18,6 +18,7 @@
<script src="~/js/note.js" asp-append-version="true"></script>
<script src="~/js/reports.js" asp-append-version="true"></script>
<script src="~/js/supplyrecord.js" asp-append-version="true"></script>
<script src="~/js/planrecord.js" asp-append-version="true"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
}
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
@@ -31,6 +32,9 @@
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-bar-chart-steps me-2"></i>Planner</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
</li>
@@ -46,9 +50,6 @@
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-shop me-2"></i>Supplies</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-bar-chart-steps me-2"></i>Planner</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>Taxes</span></button>
</li>
@@ -79,6 +80,9 @@
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps me-2"></i>Planner</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
</li>
@@ -94,9 +98,6 @@
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop me-2"></i>Supplies</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps me-2"></i>Planner</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar me-2"></i>Taxes</button>
</li>

View File

@@ -0,0 +1,43 @@
@model PlanRecord
<div class="taskCard user-select-none" draggable="true" ondragstart="dragStart(event, @Model.Id)" onclick="showEditPlanRecordModal(@Model.Id)">
<div class="card-body">
<div class="row">
<div class="col-12 col-lg-8">
<span class="taskCard-title text-truncate">@Model.Description</span>
</div>
<div class="col-12 col-lg-4 d-flex align-items-center">
<span class="text-muted text-truncate">@Model.Cost.ToString("C2")</span>
</div>
</div>
<div class="row">
<div class="col-6 col-md-1">
@if (Model.ImportMode == ImportMode.ServiceRecord)
{
<i class="bi bi-card-checklist"></i>
}
else if (Model.ImportMode == ImportMode.UpgradeRecord)
{
<i class="bi bi-wrench-adjustable"></i>
}
else if (Model.ImportMode == ImportMode.RepairRecord)
{
<i class="bi bi-exclamation-octagon"></i>
}
</div>
<div class="col-6 col-md-1">
@if (Model.Priority == PlanPriority.Critical)
{
<i class="bi bi-fire"></i>
}
else if (Model.Priority == PlanPriority.Normal)
{
<i class="bi bi-water"></i>
}
else if (Model.Priority == PlanPriority.Low)
{
<i class="bi bi-snow"></i>
}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,95 @@
@model PlanRecordInput
@{
var isNew = Model.Id == 0;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? "Add New Plan Record" : "Edit Plan Record")</h5>
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="planRecordDescription">Description</label>
<input type="text" id="planRecordDescription" class="form-control" placeholder="Describe the Plan" value="@Model.Description">
<label for="planRecordCost">Cost</label>
<input type="text" id="planRecordCost" class="form-control" placeholder="Cost of the Plan" value="@Model.Cost">
<label for="planRecordType">Type</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>Service</!option>
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>Repair</!option>
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>Upgrade</!option>
</select>
<label for="planRecordPriority">Priority</label>
<select class="form-select" id="planRecordPriority">
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>Critical</!option>
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>Normal</!option>
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>Low</!option>
</select>
<label for="planRecordProgress">Current Stage</label>
<select class="form-select" id="planRecordProgress">
<!option value="Backlog" @(Model.Progress == PlanProgress.Backlog ||isNew ? "selected" : "")>Planned</!option>
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>Doing</!option>
<!option value="Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>Testing</!option>
<!option value="Done" @(Model.Progress == PlanProgress.Done ? "selected" : "")>Done</!option>
</select>
@if (!isNew)
{
<label>Date Created: @Model.DateCreated</label>
<label>Last Modified: @Model.DateModified</label>
}
</div>
<div class="col-md-6 col-12">
<label for="planRecordNotes">Notes(optional)</label>
<textarea id="planRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="planRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
</div>
}
else
{
<label for="planRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
}
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deletePlanRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">Cancel</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle()">Add New Plan Record</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle(true)">Edit Plan Record</button>
}
</div>
<script>
var uploadedFiles = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function getPlanRecordModelData() {
return {
id: @Model.Id,
dateCreated: "@Model.DateCreated"
}
}
</script>

View File

@@ -3,10 +3,10 @@
@{
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
var hideZero = config.GetUserConfig(User).HideZero;
var backLogItems = Model.Where(x => x.Progress == CarCareTracker.Enum.PlanProgress.Backlog).OrderBy(x=>x.Priority);
var inProgressItems = Model.Where(x => x.Progress == CarCareTracker.Enum.PlanProgress.InProgress).OrderBy(x => x.Priority);
var testingItems = Model.Where(x => x.Progress == CarCareTracker.Enum.PlanProgress.Testing).OrderBy(x => x.Priority);
var doneItems = Model.Where(x => x.Progress == CarCareTracker.Enum.PlanProgress.Done).OrderBy(x => x.Priority);
var backLogItems = Model.Where(x => x.Progress == PlanProgress.Backlog).OrderBy(x=>x.Priority);
var inProgressItems = Model.Where(x => x.Progress == PlanProgress.InProgress).OrderBy(x => x.Priority);
var testingItems = Model.Where(x => x.Progress == PlanProgress.Testing).OrderBy(x => x.Priority);
var doneItems = Model.Where(x => x.Progress == PlanProgress.Done).OrderBy(x => x.Priority);
}
@model List<PlanRecord>
<div class="row">
@@ -43,39 +43,49 @@
</div>
</div>
<div class="row swimlane">
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event)">
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="lead">Planned</span>
</div>
</div>
<div class="taskCard" draggable="true" ondragstart="dragStart(event)">
<div class="card-body">
<h5 class="card-title">Test</h5>
<h6 class="card-subtitle mb-2 text-muted">$120</h6>
@foreach (PlanRecord planRecord in backLogItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
</div>
</div>
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event)">
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="lead">Doing</span>
</div>
</div>
@foreach (PlanRecord planRecord in inProgressItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event)">
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Testing')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="lead">Testing</span>
</div>
</div>
@foreach (PlanRecord planRecord in testingItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane end" ondragover="dragOver(event)" ondrop="dropBox(event)">
<div class="col-3 d-flex flex-column swimlane end" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="lead">Done</span>
</div>
</div>
@foreach (PlanRecord planRecord in doneItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
</div>
</div>
@@ -88,25 +98,3 @@
</div>
</div>
</div>
<script>
let dragged = null;
function dragEnter(event) {
event.preventDefault();
}
function dragStart(event) {
dragged = event.target;
event.dataTransfer.setData('text/plain', 1); //to-do replace with actual id.
}
function dragOver(event) {
event.preventDefault();
}
function dropBox(event) {
if ($(event.target).hasClass("swimlane")) {
if (dragged.parentElement != event.target && event.target != dragged) {
dragged.parentNode.removeChild(dragged);
event.target.appendChild(dragged);
}
}
event.preventDefault();
}
</script>

View File

@@ -272,13 +272,19 @@ input[type="file"] {
}
.taskCard {
height: 10vh;
max-height: 20vh;
padding:0.5rem;
overflow:hidden;
border-radius: 4px;
box-shadow: 0 6px 10px rgba(0,0,0,.08), 0 0 6px rgba(0,0,0,.05);
transition: .3s transform cubic-bezier(.155,1.105,.295,1.12),.3s box-shadow,.3s -webkit-transform cubic-bezier(.155,1.105,.295,1.12);
cursor: pointer;
}
.taskCard-title{
font-size:1.5rem;
font-weight:300;
max-height:10vh;
}
[data-bs-theme=dark] .taskCard {
color: #000;
background-color: rgba(255,255,255,0.5);

146
wwwroot/js/planrecord.js Normal file
View File

@@ -0,0 +1,146 @@
function showAddPlanRecordModal() {
$.get('/Vehicle/GetAddPlanRecordPartialView', function (data) {
if (data) {
$("#planRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#planRecordDate'));
$('#planRecordModal').modal('show');
}
});
}
function showEditPlanRecordModal(planRecordId) {
$.get(`/Vehicle/GetPlanRecordForEditById?planRecordId=${planRecordId}`, function (data) {
if (data) {
$("#planRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#planRecordDate'));
$('#planRecordModal').modal('show');
}
});
}
function hideAddPlanRecordModal() {
$('#planRecordModal').modal('hide');
}
function deletePlanRecord(planRecordId) {
$("#workAroundInput").show();
Swal.fire({
title: "Confirm Deletion?",
text: "Deleted Plan Records cannot be restored.",
showCancelButton: true,
confirmButtonText: "Delete",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post(`/Vehicle/DeletePlanRecordById?planRecordId=${planRecordId}`, function (data) {
if (data) {
hideAddPlanRecordModal();
successToast("Plan Record Deleted");
var vehicleId = GetVehicleId().vehicleId;
getVehiclePlanRecords(vehicleId);
} else {
errorToast("An error has occurred, please try again later.");
}
});
} else {
$("#workAroundInput").hide();
}
});
}
function savePlanRecordToVehicle(isEdit) {
//get values
var formValues = getAndValidatePlanRecordValues();
//validate
if (formValues.hasError) {
errorToast("Please check the form data");
return;
}
//save to db.
$.post('/Vehicle/SavePlanRecordToVehicleId', { planRecord: formValues }, function (data) {
if (data) {
successToast(isEdit ? "Plan Record Updated" : "Plan Record Added.");
hideAddPlanRecordModal();
saveScrollPosition();
getVehiclePlanRecords(formValues.vehicleId);
if (formValues.addReminderRecord) {
setTimeout(function () { showAddReminderModal(formValues); }, 500);
}
} else {
errorToast("An error has occurred, please try again later.");
}
})
}
function getAndValidatePlanRecordValues() {
var planDescription = $("#planRecordDescription").val();
var planCost = $("#planRecordCost").val();
var planNotes = $("#planRecordNotes").val();
var planType = $("#planRecordType").val();
var planPriority = $("#planRecordPriority").val();
var planProgress = $("#planRecordProgress").val();
var planDateCreated = getPlanRecordModelData().dateCreated;
var vehicleId = GetVehicleId().vehicleId;
var planRecordId = getPlanRecordModelData().id;
//validation
var hasError = false;
if (planDescription.trim() == '') {
hasError = true;
$("#planRecordDescription").addClass("is-invalid");
} else {
$("#planRecordDescription").removeClass("is-invalid");
}
if (planCost.trim() == '' || !isValidMoney(planCost)) {
hasError = true;
$("#planRecordCost").addClass("is-invalid");
} else {
$("#planRecordCost").removeClass("is-invalid");
}
return {
id: planRecordId,
hasError: hasError,
vehicleId: vehicleId,
dateCreated: planDateCreated,
description: planDescription,
cost: planCost,
notes: planNotes,
files: uploadedFiles,
priority: planPriority,
progress: planProgress,
importMode: planType
}
}
//drag and drop stuff.
let dragged = null;
let draggedId = 0;
function dragEnter(event) {
event.preventDefault();
}
function dragStart(event, planRecordId) {
dragged = event.target;
draggedId = planRecordId;
event.dataTransfer.setData('text/plain', draggedId);
}
function dragOver(event) {
event.preventDefault();
}
function dropBox(event, newProgress) {
if ($(event.target).hasClass("swimlane")) {
if (dragged.parentElement != event.target && event.target != dragged) {
updatePlanRecordProgress(newProgress);
}
}
event.preventDefault();
}
function updatePlanRecordProgress(newProgress) {
if (draggedId > 0) {
$.post('/Vehicle/UpdatePlanRecordProgress', { planRecordId: draggedId, planProgress: newProgress }, function (data) {
if (data) {
successToast("Plan Progress Updated");
var vehicleId = GetVehicleId().vehicleId;
getVehiclePlanRecords(vehicleId);
} else {
errorToast("An error has occurred, please try again later.");
}
});
}
draggedId = 0;
}