feat: Enhance UI/UX across multiple views with consistent styling and improved layout #3
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";
|
||||
}
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -13,36 +13,38 @@
|
|||
<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>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@
|
|||
ViewData["Title"] = "Lawn Care Log";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,3 +34,16 @@ 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