feat: Enhance UI/UX across multiple views with consistent styling and improved layout
This commit introduces significant user interface and experience improvements across the Lawn Care Log, Calendar, and Lawn Tips sections, focusing on consistent button styling, improved layout, and clearer navigation.
**Key Changes:**
* **Lawn Care Event Models:**
* Added `NotApplicable` to `MowingPattern` enum with `[Display(Name = "N/A")]` for better logging flexibility.
* **Lawn Care Log (`Views/LawnCareEvents/Index.cshtml`):**
* **Centered Page Title & Actions:** Wrapped `<h1>` and primary action buttons (`Create New Event`, `View Calendar`) in a `div` with `text-center` for better alignment.
* **Improved Search Bar Layout:** Encapsulated the search input and buttons in an `input-group` within a flex container (`d-flex justify-content-end`) with fixed width and auto margins, ensuring it's centered as a block but right-aligned internally.
* **Convert Table Actions to Buttons:** Replaced "Edit | Details | Delete" links in the main log table with `btn-sm btn-outline-primary/info/danger` styled buttons, wrapped in `d-flex flex-wrap gap-2` for consistent spacing and responsive behavior.
* Renamed "Back to Full List" to "Reset Filter" for clarity and changed its style to `btn-primary`.
* **Lawn Care Calendar (`Views/LawnCareEvents/Calendar.cshtml`):**
* **Centralized Title & Button:** Implemented a flexbox container (`d-flex justify-content-between align-items-center`) for the calendar's page title and the "Create New Event" button, ensuring they are on the same line and properly spaced.
* **Restored JavaScript Integrity:** Re-structured the top-level HTML to ensure the calendar's internal JavaScript (`#prevMonth`, `#nextMonth`, `#currentMonthYear`) remains unaffected by the page title/button layout changes.
* **Lawn Care Tips (`Views/LawnCareTip/Index.cshtml`):**
* **Consolidated Actions:** Converted "Edit | Details | Delete" links in the tips table into Bootstrap buttons (`btn-sm btn-outline-primary/info/danger`).
* **Improved Action Column Layout:** Applied new `.col-actions` CSS to the action column (`<th>` and `<td>`) to give it a fixed width and use `white-space: nowrap` to prevent action buttons from wrapping.
* **Content Column Wrapping:** Added `.col-content` CSS to the content column to limit its max-width and allow `word-wrap`.
* Changed "Create New" to "Create New Tip" with `btn-primary` styling for consistency.
* **Removed Details Link from Tips:** (Implied by diff) The Details link for `LawnCareTip` was removed from the table view, aligning with a common pattern where simple tip content might be fully displayed or only Edit/Delete actions are prioritized in the index.
* **Global Styling (`wwwroot/css/site.css`):**
* Added `col-content` and `col-actions` CSS classes to manage table column widths and wrapping behavior for cleaner table layouts.
These changes significantly improve the aesthetic consistency and usability of the application's main views.
This commit is contained in:
parent
f282b39d57
commit
557fb72081
10 changed files with 263 additions and 41 deletions
72
Migrations/20250619022142_AddNotApplicableMowingPattern.Designer.cs
generated
Normal file
72
Migrations/20250619022142_AddNotApplicableMowingPattern.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using turf_tasker.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace turf_tasker.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250619022142_AddNotApplicableMowingPattern")]
|
||||
partial class AddNotApplicableMowingPattern
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
|
||||
|
||||
modelBuilder.Entity("turf_tasker.Models.LawnCareEvent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("EventDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EventType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("MowingPattern")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("LawnCareEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("turf_tasker.Models.LawnCareTip", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Category")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("LawnCareTips");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Migrations/20250619022142_AddNotApplicableMowingPattern.cs
Normal file
22
Migrations/20250619022142_AddNotApplicableMowingPattern.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace turf_tasker.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddNotApplicableMowingPattern : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ public enum LawnCareEventType
|
|||
|
||||
public enum MowingPattern
|
||||
{
|
||||
[Display(Name = "N/A")]
|
||||
NotApplicable,
|
||||
Vertical,
|
||||
Horizontal,
|
||||
Diagonal,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@
|
|||
ViewData["Title"] = "Lawn Care Calendar";
|
||||
}
|
||||
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<a asp-action="Create" asp-controller="LawnCareEvents" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle-fill"></i> Create New Event
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
|
|
@ -27,11 +32,106 @@
|
|||
@section Scripts {
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
// TODO ADD CALENDAR JAVASCRIPT
|
||||
$(document).ready(function () {
|
||||
const calendarDaysEl = $('#calendarDays');
|
||||
const currentMonthYearEl = $('#currentMonthYear');
|
||||
const prevMonthBtn = $('#prevMonth');
|
||||
const nextMonthBtn = $('#nextMonth');
|
||||
|
||||
let currentMonth = new Date().getMonth();
|
||||
let currentYear = new Date().getFullYear();
|
||||
let allEvents = [];
|
||||
|
||||
function renderCalendar(month, year) {
|
||||
calendarDaysEl.empty();
|
||||
currentMonthYearEl.text(new Date(year, month).toLocaleString('default', { month: 'long', year: 'numeric' }));
|
||||
|
||||
const firstDayOfMonth = new Date(year, month, 1);
|
||||
const lastDayOfMonth = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastDayOfMonth.getDate();
|
||||
|
||||
const startDayOfWeek = firstDayOfMonth.getDay();
|
||||
|
||||
const prevMonthLastDay = new Date(year, month, 0).getDate();
|
||||
for (let i = startDayOfWeek; i > 0; i--) {
|
||||
const dayNum = prevMonthLastDay - i + 1;
|
||||
calendarDaysEl.append(`<div class="calendar-day other-month"><span class="day-number">${dayNum}</span></div>`);
|
||||
}
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day);
|
||||
const dateString = date.toISOString().split('T')[0];
|
||||
|
||||
let dayHtml = `<div class="calendar-day" data-date="${dateString}">`;
|
||||
dayHtml += `<span class="day-number">${day}</span>`;
|
||||
|
||||
const eventsOnThisDay = allEvents.filter(event => event.start === dateString);
|
||||
|
||||
eventsOnThisDay.forEach(event => {
|
||||
const typeClass = event.title.split(' ')[0].toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
dayHtml += `<div class="event-indicator ${typeClass}" data-event-id="${event.id}" data-event-url="${event.url}">${event.title}</div>`;
|
||||
});
|
||||
|
||||
dayHtml += `</div>`;
|
||||
calendarDaysEl.append(dayHtml);
|
||||
}
|
||||
|
||||
const totalDaysDisplayed = startDayOfWeek + daysInMonth;
|
||||
const remainingCells = 42 - totalDaysDisplayed;
|
||||
for (let i = 1; i <= remainingCells && totalDaysDisplayed + i <= 42; i++) {
|
||||
calendarDaysEl.append(`<div class="calendar-day other-month"><span class="day-number">${i}</span></div>`);
|
||||
}
|
||||
|
||||
$('.calendar-day .event-indicator').on('click', function (e) {
|
||||
e.stopPropagation();
|
||||
const url = $(this).data('event-url');
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
|
||||
$('.calendar-day:not(.other-month)').on('click', function () {
|
||||
const date = $(this).data('date');
|
||||
window.location.href = `/LawnCareEvents/Create?date=${date}`;
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchEvents() {
|
||||
try {
|
||||
const response = await fetch('/LawnCareEvents/GetEventsJson');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
allEvents = await response.json();
|
||||
renderCalendar(currentMonth, currentYear);
|
||||
} catch (error) {
|
||||
console.error('Error fetching events:', error);
|
||||
}
|
||||
}
|
||||
|
||||
prevMonthBtn.on('click', function () {
|
||||
currentMonth--;
|
||||
if (currentMonth < 0) {
|
||||
currentMonth = 11;
|
||||
currentYear--;
|
||||
}
|
||||
renderCalendar(currentMonth, currentYear);
|
||||
});
|
||||
|
||||
nextMonthBtn.on('click', function () {
|
||||
currentMonth++;
|
||||
if (currentMonth > 11) {
|
||||
currentMonth = 0;
|
||||
currentYear++;
|
||||
}
|
||||
renderCalendar(currentMonth, currentYear);
|
||||
});
|
||||
|
||||
fetchEvents();
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<!-- NEW: Link to your external calendar.css file -->
|
||||
<link href="~/css/calendar.css" rel="stylesheet" asp-append-version="true" />
|
||||
}
|
||||
|
|
@ -39,7 +39,11 @@
|
|||
|
||||
<form asp-action="Delete">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<input type="submit" value="Delete" class="btn btn-danger" /> |
|
||||
<a asp-action="Index">Back to List</a>
|
||||
<input type="submit" value="Delete" class="btn btn-danger" />
|
||||
|
||||
</form>
|
||||
|
||||
<div class="mt-3">
|
||||
<a asp-action="Index">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Save" class="btn btn-primary" />
|
||||
<a asp-action="Delete" asp-route-id="@Model.Id" class="btn btn-danger ms-2">Delete</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,72 +1,74 @@
|
|||
@model turf_tasker.Models.LawnCareEvent
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Edit";
|
||||
ViewData["Title"] = "Edit";
|
||||
}
|
||||
|
||||
<h1>Edit</h1>
|
||||
|
||||
<h4>LawnCareEvent</h4>
|
||||
<h4>Lawn Care Event</h4>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form asp-action="Edit">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div class="form-group">
|
||||
<div class="form-group mb-3">
|
||||
<label asp-for="EventType" class="control-label"></label>
|
||||
<select asp-for="EventType" class="form-control" asp-items="Html.GetEnumSelectList<LawnCareEventType>()"></select>
|
||||
<span asp-validation-for="EventType" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group mb-3">
|
||||
<label asp-for="EventDate" class="control-label"></label>
|
||||
<input asp-for="EventDate" class="form-control" />
|
||||
<span asp-validation-for="EventDate" class="text-danger"></span>
|
||||
</div>
|
||||
<div id="mowingPatternGroup">
|
||||
<div class="form-group">
|
||||
<div class="form-group mb-3">
|
||||
<label asp-for="MowingPattern" class="control-label"></label>
|
||||
<select asp-for="MowingPattern" class="form-control" asp-items="Html.GetEnumSelectList<MowingPattern>()"></select>
|
||||
<span asp-validation-for="MowingPattern" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group mb-3">
|
||||
<label asp-for="Notes" class="control-label"></label>
|
||||
<textarea asp-for="Notes" class="form-control"></textarea>
|
||||
<span asp-validation-for="Notes" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<input type="submit" value="Save" class="btn btn-primary" />
|
||||
<!-- NEW: Add a space/margin and use Razor syntax to pass the Id -->
|
||||
<a asp-action="Delete" asp-route-id="@Model.Id" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mt-3">
|
||||
<a asp-action="Index">Back to List</a>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
function toggleMowingPattern() {
|
||||
var selectedEventType = $("#EventType").val();
|
||||
function toggleMowingPattern() {
|
||||
var selectedEventType = $("#EventType").val();
|
||||
|
||||
if (selectedEventType == '0') { // '0' corresponds to Mowing
|
||||
$("#mowingPatternGroup").show();
|
||||
} else {
|
||||
$("#mowingPatternGroup").hide();
|
||||
}
|
||||
if (selectedEventType == '0') { // '0' corresponds to Mowing
|
||||
$("#mowingPatternGroup").show();
|
||||
} else {
|
||||
$("#mowingPatternGroup").hide();
|
||||
}
|
||||
}
|
||||
|
||||
toggleMowingPattern();
|
||||
|
||||
$("#EventType").on("change", function () {
|
||||
toggleMowingPattern();
|
||||
|
||||
$("#EventType").on("change", function () {
|
||||
toggleMowingPattern();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
|
@ -4,11 +4,14 @@
|
|||
ViewData["Title"] = "Lawn Care Log";
|
||||
}
|
||||
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<div class="text-center">
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>
|
||||
<a asp-action="Create" class="btn btn-primary">Create New Event</a>
|
||||
</p>
|
||||
<p>
|
||||
<a asp-action="Create" class="btn btn-primary">Create New Event</a>
|
||||
<a asp-action="Calendar" class="btn btn-info ms-2">View Calendar</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Add the search form -->
|
||||
<form asp-action="Index" method="get">
|
||||
|
|
@ -16,8 +19,8 @@
|
|||
<p>
|
||||
Find by type or note:
|
||||
<input type="text" name="searchString" value="@ViewData["CurrentFilter"]" />
|
||||
<input type="submit" value="Search" class="btn btn-secondary" /> |
|
||||
<a asp-action="Index">Back to Full List</a>
|
||||
<input type="submit" value="Search" class="btn btn-secondary" />
|
||||
<a asp-action="Index" class="btn btn-primary">Reset Filter</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -63,9 +66,11 @@
|
|||
@Html.DisplayFor(modelItem => item.Notes)
|
||||
</td>
|
||||
<td>
|
||||
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
|
||||
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
|
||||
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-outline-info">Details</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-outline-danger">Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<h1>Index</h1>
|
||||
|
||||
<p>
|
||||
<a asp-action="Create">Create New</a>
|
||||
<a asp-action="Create" class="btn btn-primary">Create New Tip</a>
|
||||
</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
|
@ -36,10 +36,11 @@
|
|||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Content)
|
||||
</td>
|
||||
<td>
|
||||
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
|
||||
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
|
||||
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
|
||||
<td class="col-actions">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-outline-danger">Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,17 @@ body {
|
|||
|
||||
.navbar-custom .nav-link:hover {
|
||||
color: #d4d4d4; /* A light gray for hover effect */
|
||||
}
|
||||
|
||||
/* For tables, to control column widths */
|
||||
.table .col-content {
|
||||
max-width: 400px; /* Adjust as needed */
|
||||
word-wrap: break-word; /* Break long words */
|
||||
white-space: normal; /* Allow text to wrap normally */
|
||||
}
|
||||
|
||||
.table .col-actions {
|
||||
width: 150px; /* Fixed width for action buttons, adjust as needed */
|
||||
min-width: 120px; /* Ensure minimum space */
|
||||
white-space: nowrap; /* Keep buttons on one line, prevent wrapping */
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue