clean up seed data and plant info setup
This commit is contained in:
@@ -553,7 +553,6 @@ export function App() {
|
||||
plants={plants}
|
||||
onCompleteBulkTasks={(tasks) => void completeBulkTasks(tasks)}
|
||||
onCompleteTask={(task) => void completeTask(task)}
|
||||
onNewPlant={startAddingPlant}
|
||||
onOpenPlant={openPlantDetail}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { CalendarCheck, Plus } from 'lucide-react';
|
||||
import { CalendarCheck } from 'lucide-react';
|
||||
import type { CareTask, Plant, PlantGroup } from '../domain';
|
||||
import { PlantCard } from './PlantCard';
|
||||
|
||||
@@ -12,7 +12,6 @@ type HomeViewProps = {
|
||||
plants: Plant[];
|
||||
onCompleteBulkTasks: (tasks: CareTask[]) => void;
|
||||
onCompleteTask: (task: CareTask) => void;
|
||||
onNewPlant: () => void;
|
||||
onOpenPlant: (plant: Plant) => void;
|
||||
};
|
||||
|
||||
@@ -25,7 +24,6 @@ export function HomeView({
|
||||
plants,
|
||||
onCompleteBulkTasks,
|
||||
onCompleteTask,
|
||||
onNewPlant,
|
||||
onOpenPlant,
|
||||
}: HomeViewProps) {
|
||||
const dueTaskGroups = groupDueTasks(careTasks);
|
||||
@@ -192,9 +190,6 @@ export function HomeView({
|
||||
<section className="section" aria-labelledby="plants-heading">
|
||||
<div className="section-heading">
|
||||
<h2 id="plants-heading">My Plants</h2>
|
||||
<button className="icon-button compact" type="button" aria-label="Add plant" onClick={onNewPlant}>
|
||||
<Plus size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="plant-grid">
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using plant_manager.Data.Models;
|
||||
|
||||
namespace plant_manager.Data
|
||||
{
|
||||
public static class DatabaseSeeder
|
||||
{
|
||||
private static readonly CareAction[] StarterCareActions =
|
||||
[
|
||||
new() { Name = "Water", Description = "Hydrate a plant or refresh its reservoir." },
|
||||
new() { Name = "Repot", Description = "Move a plant into a new container or medium." },
|
||||
new() { Name = "Fertilize", Description = "Apply nutrients or plant food." },
|
||||
new() { Name = "Prune", Description = "Trim leaves, stems, roots, or spent growth." },
|
||||
new() { Name = "Inspect", Description = "Check for pests, health changes, or growth." }
|
||||
];
|
||||
|
||||
private static readonly ActionResource[] StarterResources =
|
||||
[
|
||||
new() { Name = "Water", Notes = "Plain watering resource." },
|
||||
new() { Name = "Potting Mix", Notes = "General purpose houseplant medium." },
|
||||
new() { Name = "Orchid Bark", Notes = "Chunky amendment for airflow and drainage." },
|
||||
new() { Name = "Perlite", Notes = "Lightweight amendment for drainage and aeration." },
|
||||
new() { Name = "Fertilizer", Notes = "General plant nutrient." },
|
||||
new() { Name = "Nursery Pot", Notes = "Basic plastic grow pot." },
|
||||
new() { Name = "Neem Oil", Notes = "Common pest treatment." },
|
||||
new() { Name = "Pruners", Notes = "Cutting tool for pruning or cleanup." }
|
||||
];
|
||||
|
||||
private static readonly StarterCareActivity[] StarterCareActivities =
|
||||
[
|
||||
new("Water", [new("Water", ["Water"])]),
|
||||
new("Repot with Potting Mix", [new("Repot", ["Potting Mix"])]),
|
||||
new("Fertilize", [new("Fertilize", ["Fertilizer"])]),
|
||||
new("Prune", [new("Prune", ["Pruners"])]),
|
||||
new("Inspect", [new("Inspect", [])])
|
||||
];
|
||||
|
||||
private static readonly PlantFlagDefinition[] StarterFlagDefinitions =
|
||||
[
|
||||
new() { Name = "Spider mites", Color = "#ffe4e1" },
|
||||
new() { Name = "Fungus gnats", Color = "#fff4cc" },
|
||||
new() { Name = "Dying", Color = "#ffd6d6" },
|
||||
new() { Name = "Quarantine", Color = "#e8eef8" },
|
||||
new() { Name = "Needs repotting", Color = "#e9f5e7" },
|
||||
new() { Name = "Watch closely", Color = "#eeeeee" }
|
||||
];
|
||||
|
||||
private static readonly StarterTaxon[] StarterTaxa =
|
||||
[
|
||||
new("Chinese Money Plant", "Pilea", "peperomioides"),
|
||||
new("Croton Petra", "Codiaeum", "variegatum"),
|
||||
new("Parallel Peperomia", "Peperomia", "tetragona"),
|
||||
new("Marble Peperomia", "Peperomia", "obtusifolia"),
|
||||
new("Silver Squill", "Ledebouria", "socialis"),
|
||||
new("Fiddle-leaf Fig", "Ficus", "lyrata"),
|
||||
new("Ficus Audrey", "Ficus", "benghalensis"),
|
||||
new("Dumbcane", "Dieffenbachia", "seguine"),
|
||||
new("Kris Plant", "Alocasia", "sanderiana"),
|
||||
new("Lucky Bamboo", "Dracaena", "sanderiana"),
|
||||
new("Common Ivy", "Hedera", "helix"),
|
||||
new("Peacock Plant", "Goeppertia", "makoyana"),
|
||||
new("False Shamrock", "Oxalis", "triangularis"),
|
||||
new("Pinstripe Plant", "Goeppertia", "ornata"),
|
||||
new("Monstera Thai Constellation", "Monstera", "deliciosa"),
|
||||
new("Pothos", "Epipremnum", "aureum"),
|
||||
new("Moth orchid", "Phalaenopsis", "hybrid"),
|
||||
new("Money Tree", "Pachira", "aquatica")
|
||||
];
|
||||
|
||||
private static readonly StarterPlant[] StarterPlants =
|
||||
[
|
||||
new("Chinese Money Plant 1", "Chinese Money Plant", "Plant cart"),
|
||||
new("Chinese Money Plant 2", "Chinese Money Plant", "Plant cart"),
|
||||
new("Chinese Money Plant 3", "Chinese Money Plant", "Plant cart"),
|
||||
new("Croton Petra", "Croton Petra", "Plant cart"),
|
||||
new("Parallel Peperomia", "Parallel Peperomia", "Plant cart"),
|
||||
new("Marble Peperomia", "Marble Peperomia", "Plant cart"),
|
||||
new("Silver Squill", "Silver Squill", "Plant cart"),
|
||||
new("Fiddle-leaf Fig", "Fiddle-leaf Fig", "Living room"),
|
||||
new("Ficus Audrey", "Ficus Audrey", "Plant cart"),
|
||||
new("Dumbcane 1", "Dumbcane", "Plant cart"),
|
||||
new("Dumbcane 2", "Dumbcane", "Plant cart"),
|
||||
new("Kris Plant", "Kris Plant", "Plant cart"),
|
||||
new("Lucky Bamboo", "Lucky Bamboo", "Plant cart"),
|
||||
new("Common Ivy 1", "Common Ivy", "Plant cart"),
|
||||
new("Common Ivy 2", "Common Ivy", "Plant cart"),
|
||||
new("Peacock Plant 1", "Peacock Plant", "Plant cart"),
|
||||
new("Peacock Plant 2", "Peacock Plant", "Plant cart"),
|
||||
new("False Shamrock", "False Shamrock", "Plant cart"),
|
||||
new("Pinstripe Plant", "Pinstripe Plant", "Computer desk"),
|
||||
new("Monstera Thai Constellation", "Monstera Thai Constellation", "Plant cart"),
|
||||
new("Pothos", "Pothos", "Plant cart"),
|
||||
new("Moth orchid", "Moth orchid", "Plant cart"),
|
||||
new("Money Tree", "Money Tree", "Computer desk")
|
||||
];
|
||||
|
||||
public static void Seed(ApplicationDbContext db)
|
||||
{
|
||||
SeedCareActions(db);
|
||||
SeedActionResources(db);
|
||||
SeedCareActivities(db);
|
||||
SeedPlantFlags(db);
|
||||
SeedStarterTaxa(db);
|
||||
SeedPlantLocations(db);
|
||||
SeedStarterPlants(db);
|
||||
}
|
||||
|
||||
private static void SeedCareActions(ApplicationDbContext db)
|
||||
{
|
||||
var existingActionNames = db.CareActions
|
||||
.Select(action => action.Name)
|
||||
.ToList();
|
||||
|
||||
var missingActions = StarterCareActions
|
||||
.Where(starterAction => !existingActionNames.Any(existingName =>
|
||||
string.Equals(existingName, starterAction.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(starterAction => new CareAction
|
||||
{
|
||||
Name = starterAction.Name,
|
||||
Description = starterAction.Description
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (missingActions.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
db.CareActions.AddRange(missingActions);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void SeedActionResources(ApplicationDbContext db)
|
||||
{
|
||||
var existingResourceNames = db.ActionResources
|
||||
.Select(resource => resource.Name)
|
||||
.ToList();
|
||||
|
||||
var missingResources = StarterResources
|
||||
.Where(starterResource => !existingResourceNames.Any(existingName =>
|
||||
string.Equals(existingName, starterResource.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(starterResource => new ActionResource
|
||||
{
|
||||
Name = starterResource.Name,
|
||||
Notes = starterResource.Notes
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (missingResources.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
db.ActionResources.AddRange(missingResources);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void SeedCareActivities(ApplicationDbContext db)
|
||||
{
|
||||
var existingActivityNames = db.CareActivities
|
||||
.Select(activity => activity.Name)
|
||||
.ToList();
|
||||
var actionsByName = db.CareActions.ToDictionary(action => action.Name, StringComparer.OrdinalIgnoreCase);
|
||||
var resourcesByName = db.ActionResources.ToDictionary(resource => resource.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var missingActivities = StarterCareActivities
|
||||
.Where(starterActivity => !existingActivityNames.Any(existingName =>
|
||||
string.Equals(existingName, starterActivity.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(starterActivity =>
|
||||
{
|
||||
var actions = starterActivity.Actions
|
||||
.Select((starterAction, index) => actionsByName.TryGetValue(starterAction.Name, out var action)
|
||||
? new CareActivityAction
|
||||
{
|
||||
CareActionId = action.Id,
|
||||
CareAction = action,
|
||||
SortOrder = index,
|
||||
Resources = starterAction.ResourceNames
|
||||
.Select(resourceName => resourcesByName.TryGetValue(resourceName, out var resource)
|
||||
? new CareActivityActionResource
|
||||
{
|
||||
ActionResourceId = resource.Id,
|
||||
ActionResource = resource
|
||||
}
|
||||
: null)
|
||||
.OfType<CareActivityActionResource>()
|
||||
.ToList()
|
||||
}
|
||||
: null)
|
||||
.OfType<CareActivityAction>()
|
||||
.ToList();
|
||||
if (actions.Count != starterActivity.Actions.Count
|
||||
|| actions.Zip(starterActivity.Actions).Any(pair => pair.First.Resources.Count != pair.Second.ResourceNames.Count))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CareActivity
|
||||
{
|
||||
Name = starterActivity.Name,
|
||||
Actions = actions
|
||||
};
|
||||
})
|
||||
.OfType<CareActivity>()
|
||||
.ToList();
|
||||
|
||||
if (missingActivities.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
db.CareActivities.AddRange(missingActivities);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void SeedPlantFlags(ApplicationDbContext db)
|
||||
{
|
||||
var existingFlagNames = db.PlantFlagDefinitions
|
||||
.Select(flag => flag.Name)
|
||||
.ToList();
|
||||
|
||||
var missingFlags = StarterFlagDefinitions
|
||||
.Where(starterFlag => !existingFlagNames.Any(existingName =>
|
||||
string.Equals(existingName, starterFlag.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(starterFlag => new PlantFlagDefinition
|
||||
{
|
||||
Name = starterFlag.Name,
|
||||
Color = starterFlag.Color
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (missingFlags.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
db.PlantFlagDefinitions.AddRange(missingFlags);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void SeedStarterTaxa(ApplicationDbContext db)
|
||||
{
|
||||
var existingTaxa = db.PlantTaxa.ToList();
|
||||
var missingTaxa = StarterTaxa
|
||||
.Where(starterTaxon => !existingTaxa.Any(existingTaxon =>
|
||||
string.Equals(existingTaxon.Name, starterTaxon.Name, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(existingTaxon.Genus, starterTaxon.Genus, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(existingTaxon.Species, starterTaxon.Species, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(starterTaxon => new PlantTaxon
|
||||
{
|
||||
Name = starterTaxon.Name,
|
||||
Genus = starterTaxon.Genus,
|
||||
Species = starterTaxon.Species
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (missingTaxa.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
db.PlantTaxa.AddRange(missingTaxa);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void SeedStarterPlants(ApplicationDbContext db)
|
||||
{
|
||||
if (db.Plants.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var taxaByName = db.PlantTaxa.ToDictionary(taxon => taxon.Name, StringComparer.OrdinalIgnoreCase);
|
||||
var locationsByName = db.PlantLocations.ToDictionary(location => location.Name, StringComparer.OrdinalIgnoreCase);
|
||||
var flagsByName = db.PlantFlagDefinitions.ToDictionary(flag => flag.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
PlantTaxon Taxon(string name) => taxaByName[name];
|
||||
PlantLocation Location(string name) => locationsByName[name];
|
||||
|
||||
var starterPlants = StarterPlants
|
||||
.Select(starterPlant => new Plant
|
||||
{
|
||||
Nickname = starterPlant.Nickname,
|
||||
Taxon = Taxon(starterPlant.TaxonName),
|
||||
Location = Location(starterPlant.Location)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
db.Plants.AddRange(starterPlants);
|
||||
db.SaveChanges();
|
||||
|
||||
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
var starterFlags = new List<PlantFlag>();
|
||||
|
||||
void AddFlag(string plantNickname, string flagName)
|
||||
{
|
||||
if (!flagsByName.TryGetValue(flagName, out var flag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var plant = starterPlants.FirstOrDefault(item =>
|
||||
string.Equals(item.Nickname, plantNickname, StringComparison.OrdinalIgnoreCase));
|
||||
if (plant is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
starterFlags.Add(new PlantFlag
|
||||
{
|
||||
PlantId = plant.Id,
|
||||
PlantFlagDefinitionId = flag.Id,
|
||||
StartedOn = today
|
||||
});
|
||||
}
|
||||
|
||||
AddFlag("Pinstripe Plant", "Dying");
|
||||
AddFlag("Pinstripe Plant", "Spider mites");
|
||||
AddFlag("Pinstripe Plant", "Quarantine");
|
||||
AddFlag("Chinese Money Plant 1", "Quarantine");
|
||||
AddFlag("Chinese Money Plant 2", "Quarantine");
|
||||
AddFlag("Chinese Money Plant 3", "Quarantine");
|
||||
AddFlag("Ficus Audrey", "Needs repotting");
|
||||
|
||||
db.PlantFlags.AddRange(starterFlags);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static void SeedPlantLocations(ApplicationDbContext db)
|
||||
{
|
||||
var starterLocationNames = StarterPlants
|
||||
.Select(plant => plant.Location)
|
||||
.Append("Unassigned")
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var existingLocationNames = db.PlantLocations
|
||||
.Select(location => location.Name)
|
||||
.ToList();
|
||||
|
||||
var missingLocations = starterLocationNames
|
||||
.Where(starterLocation => !existingLocationNames.Any(existingName =>
|
||||
string.Equals(existingName, starterLocation, StringComparison.OrdinalIgnoreCase)))
|
||||
.Select(starterLocation => new PlantLocation
|
||||
{
|
||||
Name = starterLocation
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (missingLocations.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
db.PlantLocations.AddRange(missingLocations);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private sealed record StarterTaxon(string Name, string Genus, string Species);
|
||||
|
||||
private sealed record StarterCareActivity(string Name, IReadOnlyList<StarterCareActivityAction> Actions);
|
||||
|
||||
private sealed record StarterCareActivityAction(string Name, IReadOnlyList<string> ResourceNames);
|
||||
|
||||
private sealed record StarterPlant(
|
||||
string Nickname,
|
||||
string TaxonName,
|
||||
string Location);
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
db.Database.Migrate();
|
||||
DatabaseSeeder.Seed(db);
|
||||
|
||||
var plantInfoDb = scope.ServiceProvider.GetRequiredService<PlantInfoDbContext>();
|
||||
await plantInfoDb.EnsureSearchSchemaAsync();
|
||||
|
||||
@@ -6,6 +6,11 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SOURCE_DB="$REPO_ROOT/data/build/plant-info.db"
|
||||
TARGET_DB="$REPO_ROOT/plant-manager/App_Data/plant-info.db"
|
||||
|
||||
if pgrep -f "dotnet run --project plant-manager|plant-manager.dll" >/dev/null; then
|
||||
echo "Plant-Man backend appears to be running. Stop it before installing plant-info.db." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SOURCE_DB" ]]; then
|
||||
echo "Missing generated plant info database: $SOURCE_DB" >&2
|
||||
echo "Build it first with:" >&2
|
||||
@@ -13,7 +18,15 @@ if [[ ! -f "$SOURCE_DB" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_COUNT="$(sqlite3 "$SOURCE_DB" "SELECT COUNT(*) FROM PlantInfoRecords;" 2>/dev/null || echo 0)"
|
||||
if [[ "$SOURCE_COUNT" == "0" ]]; then
|
||||
echo "Generated plant info database has no PlantInfoRecords: $SOURCE_DB" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$TARGET_DB")"
|
||||
rm -f "$TARGET_DB-shm" "$TARGET_DB-wal"
|
||||
cp -f "$SOURCE_DB" "$TARGET_DB"
|
||||
|
||||
echo "Installed $SOURCE_DB -> $TARGET_DB"
|
||||
echo "Plant info records: $SOURCE_COUNT"
|
||||
|
||||
Reference in New Issue
Block a user