Merge branch 'feat/calendar-view' into 'main'
feat: Enhance UI/UX across multiple views with consistent styling and improved layout See merge request blakeridgway/turf-tasker!3
This commit is contained in:
		
						commit
						c9d3febb4c
					
				
					 16 changed files with 609 additions and 60 deletions
				
			
		|  | @ -14,19 +14,15 @@ namespace turf_tasker.Controllers | ||||||
|             _context = context; |             _context = context; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // GET: LawnCareEvents |  | ||||||
|         public async Task<IActionResult> Index(string sortOrder, string searchString) |         public async Task<IActionResult> Index(string sortOrder, string searchString) | ||||||
|         { |         { | ||||||
|             // Use ViewData to pass sort order info to the view |  | ||||||
|             ViewData["DateSortParm"] = String.IsNullOrEmpty(sortOrder) ? "date_desc" : ""; |             ViewData["DateSortParm"] = String.IsNullOrEmpty(sortOrder) ? "date_desc" : ""; | ||||||
|             ViewData["TypeSortParm"] = sortOrder == "Type" ? "type_desc" : "Type"; |             ViewData["TypeSortParm"] = sortOrder == "Type" ? "type_desc" : "Type"; | ||||||
|             ViewData["CurrentFilter"] = searchString; |             ViewData["CurrentFilter"] = searchString; | ||||||
| 
 | 
 | ||||||
|             // Start with a base query that can be modified |  | ||||||
|             var events = from e in _context.LawnCareEvents |             var events = from e in _context.LawnCareEvents | ||||||
|                 select e; |                 select e; | ||||||
| 
 | 
 | ||||||
|             // Apply the filter if a search string is provided |  | ||||||
|             if (!String.IsNullOrEmpty(searchString)) |             if (!String.IsNullOrEmpty(searchString)) | ||||||
|             { |             { | ||||||
|                 events = events.Where(e =>  |                 events = events.Where(e =>  | ||||||
|  | @ -35,7 +31,6 @@ namespace turf_tasker.Controllers | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Apply the sorting based on the sortOrder parameter |  | ||||||
|             switch (sortOrder) |             switch (sortOrder) | ||||||
|             { |             { | ||||||
|                 case "date_desc": |                 case "date_desc": | ||||||
|  | @ -51,12 +46,9 @@ namespace turf_tasker.Controllers | ||||||
|                     events = events.OrderBy(e => e.EventDate); |                     events = events.OrderBy(e => e.EventDate); | ||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // Execute the query and return the view with the filtered/sorted list |  | ||||||
|             return View(await events.AsNoTracking().ToListAsync()); |             return View(await events.AsNoTracking().ToListAsync()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // GET: LawnCareEvents/Details/5 |  | ||||||
|         public async Task<IActionResult> Details(int? id) |         public async Task<IActionResult> Details(int? id) | ||||||
|         { |         { | ||||||
|             if (id == null) |             if (id == null) | ||||||
|  | @ -74,15 +66,13 @@ namespace turf_tasker.Controllers | ||||||
|             return View(lawnCareEvent); |             return View(lawnCareEvent); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // GET: LawnCareEvents/Create |          | ||||||
|         // MODIFIED: We now create a model with a default date. |  | ||||||
|         public IActionResult Create() |         public IActionResult Create() | ||||||
|         { |         { | ||||||
|             var model = new LawnCareEvent { EventDate = DateTime.Now }; |             var model = new LawnCareEvent { EventDate = DateTime.Now }; | ||||||
|             return View(model); |             return View(model); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // POST: LawnCareEvents/Create |  | ||||||
|         [HttpPost] |         [HttpPost] | ||||||
|         [ValidateAntiForgeryToken] |         [ValidateAntiForgeryToken] | ||||||
|         public async Task<IActionResult> Create([Bind("Id,EventType,EventDate,MowingPattern,Notes")] LawnCareEvent lawnCareEvent) |         public async Task<IActionResult> Create([Bind("Id,EventType,EventDate,MowingPattern,Notes")] LawnCareEvent lawnCareEvent) | ||||||
|  | @ -96,7 +86,6 @@ namespace turf_tasker.Controllers | ||||||
|             return View(lawnCareEvent); |             return View(lawnCareEvent); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         // GET: LawnCareEvents/Edit/5 |  | ||||||
|         public async Task<IActionResult> Edit(int? id) |         public async Task<IActionResult> Edit(int? id) | ||||||
|         { |         { | ||||||
|             if (id == null) |             if (id == null) | ||||||
|  | @ -112,7 +101,6 @@ namespace turf_tasker.Controllers | ||||||
|             return View(lawnCareEvent); |             return View(lawnCareEvent); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // POST: LawnCareEvents/Edit/5 |  | ||||||
|         [HttpPost] |         [HttpPost] | ||||||
|         [ValidateAntiForgeryToken] |         [ValidateAntiForgeryToken] | ||||||
|         public async Task<IActionResult> Edit(int id, [Bind("Id,EventType,EventDate,MowingPattern,Notes")] LawnCareEvent lawnCareEvent) |         public async Task<IActionResult> Edit(int id, [Bind("Id,EventType,EventDate,MowingPattern,Notes")] LawnCareEvent lawnCareEvent) | ||||||
|  | @ -131,7 +119,7 @@ namespace turf_tasker.Controllers | ||||||
|                 } |                 } | ||||||
|                 catch (DbUpdateConcurrencyException) |                 catch (DbUpdateConcurrencyException) | ||||||
|                 { |                 { | ||||||
|                     if (!LawnCareEventExists(lawnCareEvent.Id)) |                     if (!LawnCareEventExists(lawnCareEvent.Id)) // This uses the private method below | ||||||
|                     { |                     { | ||||||
|                         return NotFound(); |                         return NotFound(); | ||||||
|                     } |                     } | ||||||
|  | @ -145,7 +133,6 @@ namespace turf_tasker.Controllers | ||||||
|             return View(lawnCareEvent); |             return View(lawnCareEvent); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // GET: LawnCareEvents/Delete/5 |  | ||||||
|         public async Task<IActionResult> Delete(int? id) |         public async Task<IActionResult> Delete(int? id) | ||||||
|         { |         { | ||||||
|             if (id == null) |             if (id == null) | ||||||
|  | @ -163,7 +150,6 @@ namespace turf_tasker.Controllers | ||||||
|             return View(lawnCareEvent); |             return View(lawnCareEvent); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // POST: LawnCareEvents/Delete/5 |  | ||||||
|         [HttpPost, ActionName("Delete")] |         [HttpPost, ActionName("Delete")] | ||||||
|         [ValidateAntiForgeryToken] |         [ValidateAntiForgeryToken] | ||||||
|         public async Task<IActionResult> DeleteConfirmed(int id) |         public async Task<IActionResult> DeleteConfirmed(int id) | ||||||
|  | @ -182,5 +168,36 @@ namespace turf_tasker.Controllers | ||||||
|         { |         { | ||||||
|             return _context.LawnCareEvents.Any(e => e.Id == id); |             return _context.LawnCareEvents.Any(e => e.Id == id); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         public IActionResult Calendar() | ||||||
|  |         { | ||||||
|  |             return View(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         [HttpGet] | ||||||
|  |         public async Task<JsonResult> GetEventsJson() | ||||||
|  |         { | ||||||
|  |             var events = await _context.LawnCareEvents.AsNoTracking().ToListAsync(); | ||||||
|  | 
 | ||||||
|  |             var calendarEvents = events.Select(e => new | ||||||
|  |             { | ||||||
|  |                 id = e.Id, | ||||||
|  |                 title = $"{e.EventType} {((e.EventType == LawnCareEventType.Mowing && e.MowingPattern.HasValue) ? $"({e.MowingPattern})" : "")}", | ||||||
|  |                 start = e.EventDate.ToString("yyyy-MM-dd"), | ||||||
|  |                 allDay = true, | ||||||
|  |                 url = $"/LawnCareEvents/Details/{e.Id}", | ||||||
|  |                 color = e.EventType switch | ||||||
|  |                 { | ||||||
|  |                     LawnCareEventType.Mowing => "#28a745", | ||||||
|  |                     LawnCareEventType.Watering => "#007bff", | ||||||
|  |                     LawnCareEventType.Fertilizing => "#ffc107", | ||||||
|  |                     LawnCareEventType.Aeration => "#6f42c1", | ||||||
|  |                     LawnCareEventType.WeedControl => "#dc3545", | ||||||
|  |                     _ => "#6c757d" | ||||||
|  |                 } | ||||||
|  |             }).ToList(); | ||||||
|  | 
 | ||||||
|  |             return Json(calendarEvents); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										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) | ||||||
|  |         { | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								Migrations/20250621212709_AddEventDetails.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Migrations/20250621212709_AddEventDetails.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | // <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("20250621212709_AddEventDetails")] | ||||||
|  |     partial class AddEventDetails | ||||||
|  |     { | ||||||
|  |         /// <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<decimal?>("AppliedAmount") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<DateTime>("EventDate") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("EventType") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<decimal?>("MowerHeightInches") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int?>("MowingPattern") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Notes") | ||||||
|  |                         .HasMaxLength(500) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProblemObserved") | ||||||
|  |                         .HasMaxLength(250) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProductUsed") | ||||||
|  |                         .HasMaxLength(200) | ||||||
|  |                         .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 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								Migrations/20250621212709_AddEventDetails.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								Migrations/20250621212709_AddEventDetails.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class AddEventDetails : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.AddColumn<decimal>( | ||||||
|  |                 name: "AppliedAmount", | ||||||
|  |                 table: "LawnCareEvents", | ||||||
|  |                 type: "TEXT", | ||||||
|  |                 nullable: true); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.AddColumn<decimal>( | ||||||
|  |                 name: "MowerHeightInches", | ||||||
|  |                 table: "LawnCareEvents", | ||||||
|  |                 type: "TEXT", | ||||||
|  |                 nullable: true); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.AddColumn<string>( | ||||||
|  |                 name: "ProblemObserved", | ||||||
|  |                 table: "LawnCareEvents", | ||||||
|  |                 type: "TEXT", | ||||||
|  |                 maxLength: 250, | ||||||
|  |                 nullable: true); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.AddColumn<string>( | ||||||
|  |                 name: "ProductUsed", | ||||||
|  |                 table: "LawnCareEvents", | ||||||
|  |                 type: "TEXT", | ||||||
|  |                 maxLength: 200, | ||||||
|  |                 nullable: true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "AppliedAmount", | ||||||
|  |                 table: "LawnCareEvents"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "MowerHeightInches", | ||||||
|  |                 table: "LawnCareEvents"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "ProblemObserved", | ||||||
|  |                 table: "LawnCareEvents"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "ProductUsed", | ||||||
|  |                 table: "LawnCareEvents"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -23,12 +23,18 @@ namespace turf_tasker.Migrations | ||||||
|                         .ValueGeneratedOnAdd() |                         .ValueGeneratedOnAdd() | ||||||
|                         .HasColumnType("INTEGER"); |                         .HasColumnType("INTEGER"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<decimal?>("AppliedAmount") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|                     b.Property<DateTime>("EventDate") |                     b.Property<DateTime>("EventDate") | ||||||
|                         .HasColumnType("TEXT"); |                         .HasColumnType("TEXT"); | ||||||
| 
 | 
 | ||||||
|                     b.Property<int>("EventType") |                     b.Property<int>("EventType") | ||||||
|                         .HasColumnType("INTEGER"); |                         .HasColumnType("INTEGER"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<decimal?>("MowerHeightInches") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|                     b.Property<int?>("MowingPattern") |                     b.Property<int?>("MowingPattern") | ||||||
|                         .HasColumnType("INTEGER"); |                         .HasColumnType("INTEGER"); | ||||||
| 
 | 
 | ||||||
|  | @ -36,6 +42,14 @@ namespace turf_tasker.Migrations | ||||||
|                         .HasMaxLength(500) |                         .HasMaxLength(500) | ||||||
|                         .HasColumnType("TEXT"); |                         .HasColumnType("TEXT"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<string>("ProblemObserved") | ||||||
|  |                         .HasMaxLength(250) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProductUsed") | ||||||
|  |                         .HasMaxLength(200) | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|                     b.HasKey("Id"); |                     b.HasKey("Id"); | ||||||
| 
 | 
 | ||||||
|                     b.ToTable("LawnCareEvents"); |                     b.ToTable("LawnCareEvents"); | ||||||
|  |  | ||||||
|  | @ -9,11 +9,17 @@ public enum LawnCareEventType | ||||||
|     Fertilizing, |     Fertilizing, | ||||||
|     Aeration, |     Aeration, | ||||||
|     WeedControl, |     WeedControl, | ||||||
|  |     PestControl, | ||||||
|  |     DiseaseControl, | ||||||
|  |     Topdressing, | ||||||
|  |     Overseeding, | ||||||
|     Other |     Other | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| public enum MowingPattern | public enum MowingPattern | ||||||
| { | { | ||||||
|  |     [Display(Name = "N/A")] | ||||||
|  |     NotApplicable, | ||||||
|     Vertical, |     Vertical, | ||||||
|     Horizontal, |     Horizontal, | ||||||
|     Diagonal, |     Diagonal, | ||||||
|  | @ -30,17 +36,30 @@ public class LawnCareEvent | ||||||
|     [Display(Name = "Activity Type")] |     [Display(Name = "Activity Type")] | ||||||
|     public LawnCareEventType EventType { get; set; } |     public LawnCareEventType EventType { get; set; } | ||||||
|      |      | ||||||
|     // --- THIS IS THE LINE TO CHECK --- |  | ||||||
|     // Make sure this property exists and is spelled correctly. |  | ||||||
|     [Required] |     [Required] | ||||||
|     [DataType(DataType.Date)] |     [DataType(DataType.Date)] | ||||||
|     [Display(Name = "Date")] |     [Display(Name = "Date")] | ||||||
|     public DateTime EventDate { get; set; } |     public DateTime EventDate { get; set; } | ||||||
|     // --------------------------------- |  | ||||||
| 
 | 
 | ||||||
|     [Display(Name = "Mowing Pattern")] |     [Display(Name = "Mowing Pattern")] | ||||||
|     public MowingPattern? MowingPattern { get; set; } |     public MowingPattern? MowingPattern { get; set; } | ||||||
|      |      | ||||||
|  |     [Display(Name = "Product Used")] | ||||||
|  |     [StringLength(200)] | ||||||
|  |     public string? ProductUsed { get; set; } | ||||||
|  | 
 | ||||||
|  |     [Display(Name = "Applied Amount")] | ||||||
|  |     [Range(0.01, 1000.0, ErrorMessage = "Amount must be positive.")] // Example range | ||||||
|  |     public decimal? AppliedAmount { get; set; } | ||||||
|  | 
 | ||||||
|  |     [Display(Name = "Mower Height (inches)")] | ||||||
|  |     [Range(0.5, 6.0, ErrorMessage = "Height must be between 0.5 and 6 inches.")] | ||||||
|  |     public decimal? MowerHeightInches { get; set; } | ||||||
|  | 
 | ||||||
|  |     [Display(Name = "Problem Observed")] | ||||||
|  |     [StringLength(250)] | ||||||
|  |     public string? ProblemObserved { get; set; } | ||||||
|  | 
 | ||||||
|     [StringLength(500)] |     [StringLength(500)] | ||||||
|     public string? Notes { get; set; } |     public string? Notes { get; set; } | ||||||
| } | } | ||||||
							
								
								
									
										137
									
								
								Views/LawnCareEvents/Calendar.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								Views/LawnCareEvents/Calendar.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | ||||||
|  | @{ | ||||||
|  |     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"> | ||||||
|  |         <button id="prevMonth" class="btn btn-secondary">< Prev</button> | ||||||
|  |         <h2 id="currentMonthYear"></h2> | ||||||
|  |         <button id="nextMonth" class="btn btn-secondary">Next ></button> | ||||||
|  |     </div> | ||||||
|  |     <div class="calendar-grid"> | ||||||
|  |         <div class="day-name">Sun</div> | ||||||
|  |         <div class="day-name">Mon</div> | ||||||
|  |         <div class="day-name">Tue</div> | ||||||
|  |         <div class="day-name">Wed</div> | ||||||
|  |         <div class="day-name">Thu</div> | ||||||
|  |         <div class="day-name">Fri</div> | ||||||
|  |         <div class="day-name">Sat</div> | ||||||
|  |     </div> | ||||||
|  |     <div id="calendarDays" class="calendar-grid"> | ||||||
|  |          | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | @section Scripts { | ||||||
|  |     <script src="~/lib/jquery/dist/jquery.min.js"></script> | ||||||
|  |     <script type="text/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 { | ||||||
|  |     <link href="~/css/calendar.css" rel="stylesheet" asp-append-version="true" /> | ||||||
|  | } | ||||||
|  | @ -39,7 +39,11 @@ | ||||||
|      |      | ||||||
|     <form asp-action="Delete"> |     <form asp-action="Delete"> | ||||||
|         <input type="hidden" asp-for="Id" /> |         <input type="hidden" asp-for="Id" /> | ||||||
|         <input type="submit" value="Delete" class="btn btn-danger" /> | |         <input type="submit" value="Delete" class="btn btn-danger" /> | ||||||
|         <a asp-action="Index">Back to List</a> | 
 | ||||||
|     </form> |     </form> | ||||||
|  |      | ||||||
|  |     <div class="mt-3"> | ||||||
|  |         <a asp-action="Index">Back to List</a> | ||||||
|  |     </div> | ||||||
| </div> | </div> | ||||||
|  | @ -43,6 +43,7 @@ | ||||||
|             </div> |             </div> | ||||||
|             <div class="form-group"> |             <div class="form-group"> | ||||||
|                 <input type="submit" value="Save" class="btn btn-primary" /> |                 <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> |             </div> | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -1,55 +1,57 @@ | ||||||
| @model turf_tasker.Models.LawnCareEvent | @model turf_tasker.Models.LawnCareEvent | ||||||
| 
 | 
 | ||||||
| @{ | @{ | ||||||
|     ViewData["Title"] = "Edit"; | ViewData["Title"] = "Edit"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| <h1>Edit</h1> | <h1>Edit</h1> | ||||||
| 
 | 
 | ||||||
| <h4>LawnCareEvent</h4> | <h4>Lawn Care Event</h4> | ||||||
| <hr /> | <hr /> | ||||||
| <div class="row"> | <div class="row"> | ||||||
|     <div class="col-md-4"> |     <div class="col-md-4"> | ||||||
|         <form asp-action="Edit"> |         <form asp-action="Edit"> | ||||||
|             <div asp-validation-summary="ModelOnly" class="text-danger"></div> |             <div asp-validation-summary="ModelOnly" class="text-danger"></div> | ||||||
|             <input type="hidden" asp-for="Id" /> |             <input type="hidden" asp-for="Id" /> | ||||||
|             <div class="form-group"> |             <div class="form-group mb-3"> | ||||||
|                 <label asp-for="EventType" class="control-label"></label> |                 <label asp-for="EventType" class="control-label"></label> | ||||||
|                 <select asp-for="EventType" class="form-control" asp-items="Html.GetEnumSelectList<LawnCareEventType>()"></select> |                 <select asp-for="EventType" class="form-control" asp-items="Html.GetEnumSelectList<LawnCareEventType>()"></select> | ||||||
|                 <span asp-validation-for="EventType" class="text-danger"></span> |                 <span asp-validation-for="EventType" class="text-danger"></span> | ||||||
|             </div> |             </div> | ||||||
|             <div class="form-group"> |             <div class="form-group mb-3"> | ||||||
|                 <label asp-for="EventDate" class="control-label"></label> |                 <label asp-for="EventDate" class="control-label"></label> | ||||||
|                 <input asp-for="EventDate" class="form-control" /> |                 <input asp-for="EventDate" class="form-control" /> | ||||||
|                 <span asp-validation-for="EventDate" class="text-danger"></span> |                 <span asp-validation-for="EventDate" class="text-danger"></span> | ||||||
|             </div> |             </div> | ||||||
|             <div id="mowingPatternGroup"> |             <div id="mowingPatternGroup"> | ||||||
|                 <div class="form-group"> |                 <div class="form-group mb-3"> | ||||||
|                     <label asp-for="MowingPattern" class="control-label"></label> |                     <label asp-for="MowingPattern" class="control-label"></label> | ||||||
|                     <select asp-for="MowingPattern" class="form-control" asp-items="Html.GetEnumSelectList<MowingPattern>()"></select> |                     <select asp-for="MowingPattern" class="form-control" asp-items="Html.GetEnumSelectList<MowingPattern>()"></select> | ||||||
|                     <span asp-validation-for="MowingPattern" class="text-danger"></span> |                     <span asp-validation-for="MowingPattern" class="text-danger"></span> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|             <div class="form-group"> |             <div class="form-group mb-3"> | ||||||
|                 <label asp-for="Notes" class="control-label"></label> |                 <label asp-for="Notes" class="control-label"></label> | ||||||
|                 <textarea asp-for="Notes" class="form-control"></textarea> |                 <textarea asp-for="Notes" class="form-control"></textarea> | ||||||
|                 <span asp-validation-for="Notes" class="text-danger"></span> |                 <span asp-validation-for="Notes" class="text-danger"></span> | ||||||
|             </div> |             </div> | ||||||
|             <div class="form-group mt-3"> |             <div class="form-group mt-3"> | ||||||
|                 <input type="submit" value="Save" class="btn btn-primary" /> |                 <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> |             </div> | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <div> | <div class="mt-3"> | ||||||
|     <a asp-action="Index">Back to List</a> |     <a asp-action="Index">Back to List</a> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| @section Scripts { | @section Scripts { | ||||||
|     @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} | ||||||
| 
 | 
 | ||||||
|     <script type="text/javascript"> | <script type="text/javascript"> | ||||||
|     $(document).ready(function () { |     $(document).ready(function () { | ||||||
| 
 | 
 | ||||||
|         function toggleMowingPattern() { |         function toggleMowingPattern() { | ||||||
|  | @ -68,5 +70,5 @@ | ||||||
|             toggleMowingPattern(); |             toggleMowingPattern(); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|     </script> | </script> | ||||||
| } | } | ||||||
|  | @ -4,11 +4,14 @@ | ||||||
|     ViewData["Title"] = "Lawn Care Log"; |     ViewData["Title"] = "Lawn Care Log"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| <h1>@ViewData["Title"]</h1> | <div class="text-center"> | ||||||
|  |     <h1>@ViewData["Title"]</h1> | ||||||
| 
 | 
 | ||||||
| <p> |     <p> | ||||||
|         <a asp-action="Create" class="btn btn-primary">Create New Event</a> |         <a asp-action="Create" class="btn btn-primary">Create New Event</a> | ||||||
| </p> |         <a asp-action="Calendar" class="btn btn-info ms-2">View Calendar</a> | ||||||
|  |     </p> | ||||||
|  | </div> | ||||||
| 
 | 
 | ||||||
| <!-- Add the search form --> | <!-- Add the search form --> | ||||||
| <form asp-action="Index" method="get"> | <form asp-action="Index" method="get"> | ||||||
|  | @ -16,8 +19,8 @@ | ||||||
|         <p> |         <p> | ||||||
|             Find by type or note:  |             Find by type or note:  | ||||||
|             <input type="text" name="searchString" value="@ViewData["CurrentFilter"]" /> |             <input type="text" name="searchString" value="@ViewData["CurrentFilter"]" /> | ||||||
|             <input type="submit" value="Search" class="btn btn-secondary" /> | |             <input type="submit" value="Search" class="btn btn-secondary" />  | ||||||
|             <a asp-action="Index">Back to Full List</a> |             <a asp-action="Index" class="btn btn-primary">Reset Filter</a> | ||||||
|         </p> |         </p> | ||||||
|     </div> |     </div> | ||||||
| </form> | </form> | ||||||
|  | @ -63,9 +66,11 @@ | ||||||
|                 @Html.DisplayFor(modelItem => item.Notes) |                 @Html.DisplayFor(modelItem => item.Notes) | ||||||
|             </td> |             </td> | ||||||
|             <td> |             <td> | ||||||
|                 <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> | |                 <div class="d-flex flex-wrap gap-2"> | ||||||
|                 <a asp-action="Details" asp-route-id="@item.Id">Details</a> | |                     <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">Delete</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> |             </td> | ||||||
|         </tr> |         </tr> | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| <h1>Index</h1> | <h1>Index</h1> | ||||||
| 
 | 
 | ||||||
| <p> | <p> | ||||||
|     <a asp-action="Create">Create New</a> |     <a asp-action="Create" class="btn btn-primary">Create New Tip</a> | ||||||
| </p> | </p> | ||||||
| <table class="table"> | <table class="table"> | ||||||
|     <thead> |     <thead> | ||||||
|  | @ -36,10 +36,11 @@ | ||||||
|             <td> |             <td> | ||||||
|                 @Html.DisplayFor(modelItem => item.Content) |                 @Html.DisplayFor(modelItem => item.Content) | ||||||
|             </td> |             </td> | ||||||
|             <td> |             <td class="col-actions"> | ||||||
|                 <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> | |                 <div class="d-flex flex-wrap gap-2"> | ||||||
|                 <a asp-action="Details" asp-route-id="@item.Id">Details</a> | |                     <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">Delete</a> |                     <a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-outline-danger">Delete</a> | ||||||
|  |                 </div> | ||||||
|             </td> |             </td> | ||||||
|         </tr> |         </tr> | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,8 @@ | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|     <title>@ViewData["Title"] - MowLog</title> |     <title>@ViewData["Title"] - MowLog</title> | ||||||
|     <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> |     <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> | ||||||
|  |     @await RenderSectionAsync("Scripts", required: false) | ||||||
|  |     @await RenderSectionAsync("Styles", required: false) | ||||||
|     <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> |     <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> | ||||||
|     <link rel="stylesheet" href="~/MowLog.styles.css" asp-append-version="true" /> |     <link rel="stylesheet" href="~/MowLog.styles.css" asp-append-version="true" /> | ||||||
| </head> | </head> | ||||||
|  | @ -46,6 +48,5 @@ | ||||||
|     <script src="~/lib/jquery/dist/jquery.min.js"></script> |     <script src="~/lib/jquery/dist/jquery.min.js"></script> | ||||||
|     <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> |     <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> | ||||||
|     <script src="~/js/site.js" asp-append-version="true"></script> |     <script src="~/js/site.js" asp-append-version="true"></script> | ||||||
|     @await RenderSectionAsync("Scripts", required: false) |  | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
							
								
								
									
										95
									
								
								wwwroot/css/calendar.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								wwwroot/css/calendar.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | .calendar-container { | ||||||
|  |     max-width: 900px; | ||||||
|  |     margin: 20px auto; | ||||||
|  |     font-family: Arial, sans-serif; | ||||||
|  |     border: 1px solid #ddd; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     overflow: hidden; | ||||||
|  |     background-color: #fff; | ||||||
|  |     box-shadow: 0 2px 5px rgba(0,0,0,0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-header { | ||||||
|  |     display: flex; | ||||||
|  |     justify-content: space-between; | ||||||
|  |     align-items: center; | ||||||
|  |     background-color: #f8f9fa; | ||||||
|  |     padding: 15px; | ||||||
|  |     border-bottom: 1px solid #ddd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-header h2 { | ||||||
|  |     margin: 0; | ||||||
|  |     font-size: 1.5em; | ||||||
|  |     color: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-grid { | ||||||
|  |     display: grid; | ||||||
|  |     grid-template-columns: repeat(7, 1fr); | ||||||
|  |     gap: 1px; | ||||||
|  |     background-color: #ddd; | ||||||
|  |     border-bottom: 1px solid #ddd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .day-name { | ||||||
|  |     background-color: #e9ecef; | ||||||
|  |     padding: 10px 5px; | ||||||
|  |     text-align: center; | ||||||
|  |     font-weight: bold; | ||||||
|  |     color: #555; | ||||||
|  |     font-size: 0.9em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-day { | ||||||
|  |     background-color: #fff; | ||||||
|  |     padding: 10px 5px; | ||||||
|  |     min-height: 100px;  | ||||||
|  |     text-align: right; | ||||||
|  |     position: relative; | ||||||
|  |     cursor: pointer; | ||||||
|  |     transition: background-color 0.2s ease; | ||||||
|  |     box-sizing: border-box;  | ||||||
|  |     border: 1px solid #eee;  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-day:hover { | ||||||
|  |     background-color: #f0f0f0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-day.other-month { | ||||||
|  |     background-color: #f9f9f9; | ||||||
|  |     color: #ccc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-day .day-number { | ||||||
|  |     font-weight: bold; | ||||||
|  |     font-size: 1.2em; | ||||||
|  |     color: #333; | ||||||
|  |     position: absolute; | ||||||
|  |     top: 5px; | ||||||
|  |     right: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .calendar-day .event-indicator { | ||||||
|  |     background-color: #28a745; | ||||||
|  |     color: white; | ||||||
|  |     font-size: 0.75em; | ||||||
|  |     padding: 2px 5px; | ||||||
|  |     border-radius: 3px; | ||||||
|  |     margin-top: 25px; | ||||||
|  |     display: inline-block; | ||||||
|  |     margin-left: 5px; | ||||||
|  |     margin-right: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .event-indicator.mowing { background-color: #28a745; } | ||||||
|  | .event-indicator.watering { background-color: #007bff; } | ||||||
|  | .event-indicator.fertilizing { background-color: #ffc107; } | ||||||
|  | .event-indicator.aeration { background-color: #6f42c1; } | ||||||
|  | .event-indicator.weedcontrol { background-color: #dc3545; } | ||||||
|  | .event-indicator.other { background-color: #6c757d; } | ||||||
|  | 
 | ||||||
|  | .card.h-100 { | ||||||
|  |     height: 100% !important; | ||||||
|  | } | ||||||
|  | @ -34,3 +34,16 @@ body { | ||||||
| .navbar-custom .nav-link:hover { | .navbar-custom .nav-link:hover { | ||||||
|   color: #d4d4d4; /* A light gray for hover effect */ |   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
	
	 Blake Ridgway
						Blake Ridgway