feat: Add Lawn Care Tips section and improve Dashboard layout #1
					 17 changed files with 689 additions and 18 deletions
				
			
		|  | @ -10,22 +10,18 @@ public class HomeController : Controller | ||||||
| { | { | ||||||
|     private readonly ApplicationDbContext _context; |     private readonly ApplicationDbContext _context; | ||||||
| 
 | 
 | ||||||
|     // Step 2.1: Inject the database context |  | ||||||
|     public HomeController(ApplicationDbContext context) |     public HomeController(ApplicationDbContext context) | ||||||
|     { |     { | ||||||
|         _context = context; |         _context = context; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Step 2.2: Make the Index action async |  | ||||||
|     public async Task<IActionResult> Index() |     public async Task<IActionResult> Index() | ||||||
|     { |     { | ||||||
|         // Step 2.3: Query for the last mow event to get its date and pattern |  | ||||||
|         var lastMowEvent = await _context.LawnCareEvents |         var lastMowEvent = await _context.LawnCareEvents | ||||||
|             .Where(e => e.EventType == LawnCareEventType.Mowing) |             .Where(e => e.EventType == LawnCareEventType.Mowing) | ||||||
|             .OrderByDescending(e => e.EventDate) |             .OrderByDescending(e => e.EventDate) | ||||||
|             .FirstOrDefaultAsync(); |             .FirstOrDefaultAsync(); | ||||||
| 
 | 
 | ||||||
|         // Step 2.4: Query for the last water and fertilize dates |  | ||||||
|         var lastWaterDate = await _context.LawnCareEvents |         var lastWaterDate = await _context.LawnCareEvents | ||||||
|             .Where(e => e.EventType == LawnCareEventType.Watering) |             .Where(e => e.EventType == LawnCareEventType.Watering) | ||||||
|             .OrderByDescending(e => e.EventDate) |             .OrderByDescending(e => e.EventDate) | ||||||
|  | @ -38,6 +34,12 @@ public class HomeController : Controller | ||||||
|             .Select(e => (DateTime?)e.EventDate) |             .Select(e => (DateTime?)e.EventDate) | ||||||
|             .FirstOrDefaultAsync(); |             .FirstOrDefaultAsync(); | ||||||
| 
 | 
 | ||||||
|  |         var lastAerationDate = await _context.LawnCareEvents | ||||||
|  |             .Where(e => e.EventType == LawnCareEventType.Aeration) | ||||||
|  |             .OrderByDescending(e => e.EventDate) | ||||||
|  |             .Select(e => (DateTime?)e.EventDate) | ||||||
|  |             .FirstOrDefaultAsync(); | ||||||
|  |          | ||||||
|         // Determine the next mowing pattern |         // Determine the next mowing pattern | ||||||
|         MowingPattern? nextPattern = MowingPattern.Vertical; // Default |         MowingPattern? nextPattern = MowingPattern.Vertical; // Default | ||||||
|         if (lastMowEvent?.MowingPattern != null) |         if (lastMowEvent?.MowingPattern != null) | ||||||
|  | @ -48,16 +50,15 @@ public class HomeController : Controller | ||||||
|             nextPattern = (MowingPattern)nextPatternValue; |             nextPattern = (MowingPattern)nextPatternValue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Step 2.5: Create the ViewModel and populate it |  | ||||||
|         var viewModel = new DashboardViewModel |         var viewModel = new DashboardViewModel | ||||||
|         { |         { | ||||||
|             LastMowDate = lastMowEvent?.EventDate, |             LastMowDate = lastMowEvent?.EventDate, | ||||||
|             LastWaterDate = lastWaterDate, |             LastWaterDate = lastWaterDate, | ||||||
|             LastFertilizeDate = lastFertilizeDate, |             LastFertilizeDate = lastFertilizeDate, | ||||||
|  |             LastAerationDate = lastAerationDate, | ||||||
|             NextMowingPattern = nextPattern |             NextMowingPattern = nextPattern | ||||||
|         }; |         }; | ||||||
|          |          | ||||||
|         // Step 2.6: Pass the populated ViewModel to the view |  | ||||||
|         return View(viewModel); |         return View(viewModel); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										157
									
								
								Controllers/LawnCareTipController.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								Controllers/LawnCareTipController.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,157 @@ | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Microsoft.AspNetCore.Mvc.Rendering; | ||||||
|  | using Microsoft.EntityFrameworkCore; | ||||||
|  | using turf_tasker.Data; | ||||||
|  | using turf_tasker.Models; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Controllers | ||||||
|  | { | ||||||
|  |     public class LawnCareTipController : Controller | ||||||
|  |     { | ||||||
|  |         private readonly ApplicationDbContext _context; | ||||||
|  | 
 | ||||||
|  |         public LawnCareTipController(ApplicationDbContext context) | ||||||
|  |         { | ||||||
|  |             _context = context; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // GET: LawnCareTip | ||||||
|  |         public async Task<IActionResult> Index() | ||||||
|  |         { | ||||||
|  |             return View(await _context.LawnCareTips.ToListAsync()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // GET: LawnCareTip/Details/5 | ||||||
|  |         public async Task<IActionResult> Details(int? id) | ||||||
|  |         { | ||||||
|  |             if (id == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var lawnCareTip = await _context.LawnCareTips | ||||||
|  |                 .FirstOrDefaultAsync(m => m.Id == id); | ||||||
|  |             if (lawnCareTip == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return View(lawnCareTip); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // GET: LawnCareTip/Create | ||||||
|  |         public IActionResult Create() | ||||||
|  |         { | ||||||
|  |             return View(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // POST: LawnCareTip/Create | ||||||
|  |         // To protect from overposting attacks, enable the specific properties you want to bind to. | ||||||
|  |         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598. | ||||||
|  |         [HttpPost] | ||||||
|  |         [ValidateAntiForgeryToken] | ||||||
|  |         public async Task<IActionResult> Create([Bind("Id,Title,Category,Content")] LawnCareTip lawnCareTip) | ||||||
|  |         { | ||||||
|  |             if (ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 _context.Add(lawnCareTip); | ||||||
|  |                 await _context.SaveChangesAsync(); | ||||||
|  |                 return RedirectToAction(nameof(Index)); | ||||||
|  |             } | ||||||
|  |             return View(lawnCareTip); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // GET: LawnCareTip/Edit/5 | ||||||
|  |         public async Task<IActionResult> Edit(int? id) | ||||||
|  |         { | ||||||
|  |             if (id == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var lawnCareTip = await _context.LawnCareTips.FindAsync(id); | ||||||
|  |             if (lawnCareTip == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  |             return View(lawnCareTip); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // POST: LawnCareTip/Edit/5 | ||||||
|  |         // To protect from overposting attacks, enable the specific properties you want to bind to. | ||||||
|  |         // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598. | ||||||
|  |         [HttpPost] | ||||||
|  |         [ValidateAntiForgeryToken] | ||||||
|  |         public async Task<IActionResult> Edit(int id, [Bind("Id,Title,Category,Content")] LawnCareTip lawnCareTip) | ||||||
|  |         { | ||||||
|  |             if (id != lawnCareTip.Id) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (ModelState.IsValid) | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     _context.Update(lawnCareTip); | ||||||
|  |                     await _context.SaveChangesAsync(); | ||||||
|  |                 } | ||||||
|  |                 catch (DbUpdateConcurrencyException) | ||||||
|  |                 { | ||||||
|  |                     if (!LawnCareTipExists(lawnCareTip.Id)) | ||||||
|  |                     { | ||||||
|  |                         return NotFound(); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         throw; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return RedirectToAction(nameof(Index)); | ||||||
|  |             } | ||||||
|  |             return View(lawnCareTip); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // GET: LawnCareTip/Delete/5 | ||||||
|  |         public async Task<IActionResult> Delete(int? id) | ||||||
|  |         { | ||||||
|  |             if (id == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var lawnCareTip = await _context.LawnCareTips | ||||||
|  |                 .FirstOrDefaultAsync(m => m.Id == id); | ||||||
|  |             if (lawnCareTip == null) | ||||||
|  |             { | ||||||
|  |                 return NotFound(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return View(lawnCareTip); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // POST: LawnCareTip/Delete/5 | ||||||
|  |         [HttpPost, ActionName("Delete")] | ||||||
|  |         [ValidateAntiForgeryToken] | ||||||
|  |         public async Task<IActionResult> DeleteConfirmed(int id) | ||||||
|  |         { | ||||||
|  |             var lawnCareTip = await _context.LawnCareTips.FindAsync(id); | ||||||
|  |             if (lawnCareTip != null) | ||||||
|  |             { | ||||||
|  |                 _context.LawnCareTips.Remove(lawnCareTip); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             await _context.SaveChangesAsync(); | ||||||
|  |             return RedirectToAction(nameof(Index)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private bool LawnCareTipExists(int id) | ||||||
|  |         { | ||||||
|  |             return _context.LawnCareTips.Any(e => e.Id == id); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -9,4 +9,6 @@ public class ApplicationDbContext : DbContext | ||||||
|         : base(options) { } |         : base(options) { } | ||||||
|      |      | ||||||
|     public DbSet<LawnCareEvent> LawnCareEvents { get; set; } |     public DbSet<LawnCareEvent> LawnCareEvents { get; set; } | ||||||
|  |      | ||||||
|  |     public DbSet<LawnCareTip> LawnCareTips { get; set; } | ||||||
| } | } | ||||||
							
								
								
									
										72
									
								
								Migrations/20250617225541_AddLawnCareTips.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Migrations/20250617225541_AddLawnCareTips.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("20250617225541_AddLawnCareTips")] | ||||||
|  |     partial class AddLawnCareTips | ||||||
|  |     { | ||||||
|  |         /// <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 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								Migrations/20250617225541_AddLawnCareTips.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Migrations/20250617225541_AddLawnCareTips.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class AddLawnCareTips : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "LawnCareTips", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|  |                         .Annotation("Sqlite:Autoincrement", true), | ||||||
|  |                     Title = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false), | ||||||
|  |                     Category = table.Column<int>(type: "INTEGER", nullable: false), | ||||||
|  |                     Content = table.Column<string>(type: "TEXT", nullable: false) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_LawnCareTips", x => x.Id); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "LawnCareTips"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								Migrations/20250617225855_SeedBermudaTips.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								Migrations/20250617225855_SeedBermudaTips.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("20250617225855_SeedBermudaTips")] | ||||||
|  |     partial class SeedBermudaTips | ||||||
|  |     { | ||||||
|  |         /// <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 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								Migrations/20250617225855_SeedBermudaTips.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								Migrations/20250617225855_SeedBermudaTips.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Migrations | ||||||
|  | { | ||||||
|  | public partial class SeedBermudaTips : Migration | ||||||
|  |     { | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             // Seed data for LawnCareTips | ||||||
|  |             migrationBuilder.InsertData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 columns: new[] { "Id", "Title", "Category", "Content" }, | ||||||
|  |                 values: new object[,] | ||||||
|  |                 { | ||||||
|  |                     { 1, "Bermuda Mowing Height", 0, "For Bermuda grass in Oklahoma, mow at a height of 1-2 inches for optimal density and health during peak season." }, | ||||||
|  |                     { 2, "Bermuda Mowing Frequency", 0, "During peak growing season (late spring/summer), mow Bermuda grass every 3-5 days. Frequent mowing prevents scalping." }, | ||||||
|  |                     { 3, "Bermuda Watering - Deep & Infrequent", 1, "Water Bermuda grass deeply (1-1.5 inches) once or twice a week, rather than frequent, shallow watering. Water in the early morning." }, | ||||||
|  |                     { 4, "Bermuda Spring Fertilization", 2, "Apply a nitrogen-heavy fertilizer in late spring (May/June) after Bermuda grass has fully greened up and soil temperatures are consistently above 65°F (18°C)." }, | ||||||
|  |                     { 5, "Bermuda Summer Fertilization", 2, "A second application of nitrogen fertilizer in mid-summer (July/August) can further promote vigorous growth for Bermuda grass." }, | ||||||
|  |                     { 6, "Pre-Emergent Weed Control", 3, "Apply a pre-emergent herbicide for summer annual weeds like crabgrass in early spring (March-April) before soil temperatures consistently reach 50-55°F (10-13°C) in Oklahoma." }, | ||||||
|  |                     { 7, "Post-Emergent Weed Control", 3, "Treat broadleaf weeds in Bermuda grass as needed throughout the growing season with a selective post-emergent herbicide. Always follow product instructions carefully." }, | ||||||
|  |                     { 8, "Aeration for Bermuda", 4, "Aerate Bermuda grass in late spring to early summer when it's actively growing. This helps reduce compaction and improve nutrient absorption." }, | ||||||
|  |                     { 9, "Bermuda Winter Dormancy", 5, "Bermuda grass will go dormant and turn brown in winter; this is normal. Avoid heavy foot traffic when it is dormant to prevent damage." } | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             // Remove the seeded data if the migration is reverted | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 1); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 2); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 3); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 4); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 5); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 6); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 7); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 8); | ||||||
|  |             migrationBuilder.DeleteData( | ||||||
|  |                 table: "LawnCareTips", | ||||||
|  |                 keyColumn: "Id", | ||||||
|  |                 keyValue: 9); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -40,6 +40,29 @@ namespace turf_tasker.Migrations | ||||||
| 
 | 
 | ||||||
|                     b.ToTable("LawnCareEvents"); |                     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 | #pragma warning restore 612, 618 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								Models/LawnCareTip.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Models/LawnCareTip.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | 
 | ||||||
|  | namespace turf_tasker.Models; | ||||||
|  | 
 | ||||||
|  | public enum TipCategory | ||||||
|  | { | ||||||
|  |     Mowing, | ||||||
|  |     Watering, | ||||||
|  |     Fertilizing, | ||||||
|  |     [Display(Name = "Weed Control")] | ||||||
|  |     WeedControl, | ||||||
|  |     Aeration, | ||||||
|  |     General | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | public class LawnCareTip | ||||||
|  | { | ||||||
|  |     public int Id { get; set; } | ||||||
|  |      | ||||||
|  |     [Required] | ||||||
|  |     [StringLength(100)] | ||||||
|  |     public string Title { get; set; } = string.Empty; | ||||||
|  |      | ||||||
|  |     [Required] | ||||||
|  |     public TipCategory Category { get; set; } | ||||||
|  |      | ||||||
|  |     [Required] | ||||||
|  |     [DataType(DataType.MultilineText)] | ||||||
|  |     public string Content { get; set; } = string.Empty; | ||||||
|  | } | ||||||
|  | @ -90,16 +90,17 @@ | ||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
|     <!-- Add New Event Card --> |     <!-- Add New Event Card --> | ||||||
|     <div class="col-md-3"> |     <div class="row mt-4 text-center"> | ||||||
|         <div class="card h-100 bg-light"> |         <div class="col-md-3"> | ||||||
|             <div class="card-body d-flex flex-column justify-content-center"> |             <div class="card h-100 bg-light"> | ||||||
|                 <h5 class="card-title">Log a New Activity</h5> |                 <div class="card-body d-flex flex-column justify-content-center"> | ||||||
|                 <p class="card-text">Keep your dashboard up to date.</p> |                     <h5 class="card-title">Log a New Activity</h5> | ||||||
|                 <a asp-controller="LawnCareEvents" asp-action="Create" class="btn btn-primary mt-auto"> |                     <p class="card-text">Keep your dashboard up to date.</p> | ||||||
|                     Add New Event |                     <a asp-controller="LawnCareEvents" asp-action="Create" class="btn btn-primary mt-auto"> | ||||||
|                 </a> |                         Add New Event | ||||||
|  |                     </a> | ||||||
|  |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| 
 |  | ||||||
| </div> | </div> | ||||||
							
								
								
									
										40
									
								
								Views/LawnCareTip/Create.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Views/LawnCareTip/Create.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | @model turf_tasker.Models.LawnCareTip | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Create"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>Create</h1> | ||||||
|  | 
 | ||||||
|  | <h4>LawnCareTip</h4> | ||||||
|  | <hr /> | ||||||
|  | <div class="row"> | ||||||
|  |     <div class="col-md-4"> | ||||||
|  |         <form asp-action="Create"> | ||||||
|  |             <div asp-validation-summary="ModelOnly" class="text-danger"></div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <label asp-for="Title" class="control-label"></label> | ||||||
|  |                 <input asp-for="Title" class="form-control" /> | ||||||
|  |                 <span asp-validation-for="Title" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <label asp-for="Category" class="control-label"></label> | ||||||
|  |                 <select asp-for="Category" class="form-control"></select> | ||||||
|  |                 <span asp-validation-for="Category" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <label asp-for="Content" class="control-label"></label> | ||||||
|  |                 <textarea asp-for="Content" class="form-control"></textarea> | ||||||
|  |                 <span asp-validation-for="Content" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <input type="submit" value="Create" class="btn btn-primary" /> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div> | ||||||
|  |     <a asp-action="Index">Back to List</a> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
							
								
								
									
										39
									
								
								Views/LawnCareTip/Delete.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Views/LawnCareTip/Delete.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | @model turf_tasker.Models.LawnCareTip | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Delete"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>Delete</h1> | ||||||
|  | 
 | ||||||
|  | <h3>Are you sure you want to delete this?</h3> | ||||||
|  | <div> | ||||||
|  |     <h4>LawnCareTip</h4> | ||||||
|  |     <hr /> | ||||||
|  |     <dl class="row"> | ||||||
|  |         <dt class = "col-sm-2"> | ||||||
|  |             @Html.DisplayNameFor(model => model.Title) | ||||||
|  |         </dt> | ||||||
|  |         <dd class = "col-sm-10"> | ||||||
|  |             @Html.DisplayFor(model => model.Title) | ||||||
|  |         </dd> | ||||||
|  |         <dt class = "col-sm-2"> | ||||||
|  |             @Html.DisplayNameFor(model => model.Category) | ||||||
|  |         </dt> | ||||||
|  |         <dd class = "col-sm-10"> | ||||||
|  |             @Html.DisplayFor(model => model.Category) | ||||||
|  |         </dd> | ||||||
|  |         <dt class = "col-sm-2"> | ||||||
|  |             @Html.DisplayNameFor(model => model.Content) | ||||||
|  |         </dt> | ||||||
|  |         <dd class = "col-sm-10"> | ||||||
|  |             @Html.DisplayFor(model => model.Content) | ||||||
|  |         </dd> | ||||||
|  |     </dl> | ||||||
|  |      | ||||||
|  |     <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> | ||||||
|  |     </form> | ||||||
|  | </div> | ||||||
							
								
								
									
										36
									
								
								Views/LawnCareTip/Details.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Views/LawnCareTip/Details.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | @model turf_tasker.Models.LawnCareTip | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Details"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>Details</h1> | ||||||
|  | 
 | ||||||
|  | <div> | ||||||
|  |     <h4>LawnCareTip</h4> | ||||||
|  |     <hr /> | ||||||
|  |     <dl class="row"> | ||||||
|  |         <dt class = "col-sm-2"> | ||||||
|  |             @Html.DisplayNameFor(model => model.Title) | ||||||
|  |         </dt> | ||||||
|  |         <dd class = "col-sm-10"> | ||||||
|  |             @Html.DisplayFor(model => model.Title) | ||||||
|  |         </dd> | ||||||
|  |         <dt class = "col-sm-2"> | ||||||
|  |             @Html.DisplayNameFor(model => model.Category) | ||||||
|  |         </dt> | ||||||
|  |         <dd class = "col-sm-10"> | ||||||
|  |             @Html.DisplayFor(model => model.Category) | ||||||
|  |         </dd> | ||||||
|  |         <dt class = "col-sm-2"> | ||||||
|  |             @Html.DisplayNameFor(model => model.Content) | ||||||
|  |         </dt> | ||||||
|  |         <dd class = "col-sm-10"> | ||||||
|  |             @Html.DisplayFor(model => model.Content) | ||||||
|  |         </dd> | ||||||
|  |     </dl> | ||||||
|  | </div> | ||||||
|  | <div> | ||||||
|  |     <a asp-action="Edit" asp-route-id="@Model?.Id">Edit</a> | | ||||||
|  |     <a asp-action="Index">Back to List</a> | ||||||
|  | </div> | ||||||
							
								
								
									
										41
									
								
								Views/LawnCareTip/Edit.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Views/LawnCareTip/Edit.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | @model turf_tasker.Models.LawnCareTip | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Edit"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>Edit</h1> | ||||||
|  | 
 | ||||||
|  | <h4>LawnCareTip</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"> | ||||||
|  |                 <label asp-for="Title" class="control-label"></label> | ||||||
|  |                 <input asp-for="Title" class="form-control" /> | ||||||
|  |                 <span asp-validation-for="Title" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <label asp-for="Category" class="control-label"></label> | ||||||
|  |                 <select asp-for="Category" class="form-control"></select> | ||||||
|  |                 <span asp-validation-for="Category" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <label asp-for="Content" class="control-label"></label> | ||||||
|  |                 <textarea asp-for="Content" class="form-control"></textarea> | ||||||
|  |                 <span asp-validation-for="Content" class="text-danger"></span> | ||||||
|  |             </div> | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <input type="submit" value="Save" class="btn btn-primary" /> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div> | ||||||
|  |     <a asp-action="Index">Back to List</a> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
							
								
								
									
										47
									
								
								Views/LawnCareTip/Index.cshtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Views/LawnCareTip/Index.cshtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | ||||||
|  | @model IEnumerable<turf_tasker.Models.LawnCareTip> | ||||||
|  | 
 | ||||||
|  | @{ | ||||||
|  |     ViewData["Title"] = "Index"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | <h1>Index</h1> | ||||||
|  | 
 | ||||||
|  | <p> | ||||||
|  |     <a asp-action="Create">Create New</a> | ||||||
|  | </p> | ||||||
|  | <table class="table"> | ||||||
|  |     <thead> | ||||||
|  |         <tr> | ||||||
|  |             <th> | ||||||
|  |                 @Html.DisplayNameFor(model => model.Title) | ||||||
|  |             </th> | ||||||
|  |             <th> | ||||||
|  |                 @Html.DisplayNameFor(model => model.Category) | ||||||
|  |             </th> | ||||||
|  |             <th> | ||||||
|  |                 @Html.DisplayNameFor(model => model.Content) | ||||||
|  |             </th> | ||||||
|  |             <th></th> | ||||||
|  |         </tr> | ||||||
|  |     </thead> | ||||||
|  |     <tbody> | ||||||
|  | @foreach (var item in Model) { | ||||||
|  |         <tr> | ||||||
|  |             <td> | ||||||
|  |                 @Html.DisplayFor(modelItem => item.Title) | ||||||
|  |             </td> | ||||||
|  |             <td> | ||||||
|  |                 @Html.DisplayFor(modelItem => item.Category) | ||||||
|  |             </td> | ||||||
|  |             <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> | ||||||
|  |         </tr> | ||||||
|  | } | ||||||
|  |     </tbody> | ||||||
|  | </table> | ||||||
|  | @ -12,7 +12,6 @@ | ||||||
|     <header> |     <header> | ||||||
|         <nav class="navbar navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3 navbar-custom"> |         <nav class="navbar navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3 navbar-custom"> | ||||||
|             <div class="container-fluid"> |             <div class="container-fluid"> | ||||||
|                 <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MowLog</a> |  | ||||||
|                 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" |                 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" | ||||||
|                         aria-expanded="false" aria-label="Toggle navigation"> |                         aria-expanded="false" aria-label="Toggle navigation"> | ||||||
|                     <span class="navbar-toggler-icon"></span> |                     <span class="navbar-toggler-icon"></span> | ||||||
|  | @ -20,11 +19,14 @@ | ||||||
|                 <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> |                 <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> | ||||||
|                     <ul class="navbar-nav flex-grow-1"> |                     <ul class="navbar-nav flex-grow-1"> | ||||||
|                         <li class="nav-item"> |                         <li class="nav-item"> | ||||||
|                             <a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">Home</a> |                             <a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">Dashboard</a> | ||||||
|                         </li> |                         </li> | ||||||
|                         <li class="nav-item"> |                         <li class="nav-item"> | ||||||
|                             <a class="nav-link" asp-area="" asp-controller="LawnCareEvents" asp-action="Index">Mow Log</a> |                             <a class="nav-link" asp-area="" asp-controller="LawnCareEvents" asp-action="Index">Mow Log</a> | ||||||
|                         </li> |                         </li> | ||||||
|  |                         <li class="nav-item"> | ||||||
|  |                             <a class="nav-link" asp-area="" asp-controller="LawnCareTip" asp-action="Index">Lawn Tips</a> | ||||||
|  |                         </li> | ||||||
|                     </ul> |                     </ul> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|  | @ -8,10 +8,12 @@ | ||||||
| 
 | 
 | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" /> |       <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" /> | ||||||
|  |       <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" /> | ||||||
|       <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6"> |       <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6"> | ||||||
|         <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |         <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|         <PrivateAssets>all</PrivateAssets> |         <PrivateAssets>all</PrivateAssets> | ||||||
|       </PackageReference> |       </PackageReference> | ||||||
|  |       <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
| 
 | 
 | ||||||
| </Project> | </Project> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue