Fix tag search, add text name search, add config around seeding
This commit is contained in:
parent
38bedbaa72
commit
b19f46ff92
@ -133,10 +133,6 @@ More for the frontend, but backend doesn't have the best either. More checks mak
|
|||||||
|
|
||||||
Even if it's just a few, it will show off how I unit test for the frontend.
|
Even if it's just a few, it will show off how I unit test for the frontend.
|
||||||
|
|
||||||
### A little better configuration for seeding ect
|
### Cors?
|
||||||
|
|
||||||
Just to say I have.
|
Cors may be an issue here.
|
||||||
|
|
||||||
### Make tags work
|
|
||||||
|
|
||||||
The last big thing.
|
|
@ -15,9 +15,12 @@ public class VenueController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetVenues([FromQuery] string[]? categories = null, [FromQuery] string[]? tags = null)
|
public async Task<IActionResult> GetVenues(
|
||||||
|
[FromQuery] string? search = null,
|
||||||
|
[FromQuery] string[]? categories = null,
|
||||||
|
[FromQuery] string[]? tags = null)
|
||||||
{
|
{
|
||||||
var venues = await _venueService.GetFilteredVenues(null, categories);
|
var venues = await _venueService.GetFilteredVenues(search, tags, categories);
|
||||||
|
|
||||||
return Ok(venues);
|
return Ok(venues);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using CsvHelper;
|
using CsvHelper;
|
||||||
using LeedsBeerQuest.API.Data.Contexts;
|
using LeedsBeerQuest.API.Data.Contexts;
|
||||||
using LeedsBeerQuest.API.Data.Models;
|
using LeedsBeerQuest.API.Data.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace LeedsBeerQuest.API.Data.Seed;
|
namespace LeedsBeerQuest.API.Data.Seed;
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ public class LeedsBeerQuestSeeder
|
|||||||
|
|
||||||
IEnumerable<LeedsBeerQuestCSV>? lbqCSV;
|
IEnumerable<LeedsBeerQuestCSV>? lbqCSV;
|
||||||
|
|
||||||
using (var reader = new StreamReader("../leedsbeerquest.csv"))
|
using (var reader = new StreamReader(_dataSourcePath))
|
||||||
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
|
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
|
||||||
{
|
{
|
||||||
lbqCSV = csv.GetRecords<LeedsBeerQuestCSV>().ToList();
|
lbqCSV = csv.GetRecords<LeedsBeerQuestCSV>().ToList();
|
||||||
|
@ -6,5 +6,5 @@ public interface IVenueService
|
|||||||
{
|
{
|
||||||
public Task<IEnumerable<Venue>> GetAllVenues();
|
public Task<IEnumerable<Venue>> GetAllVenues();
|
||||||
public Task<Venue?> GetVenue(int venueId);
|
public Task<Venue?> GetVenue(int venueId);
|
||||||
public Task<IEnumerable<Venue>> GetFilteredVenues(string[]? tags = null, string[]? categories = null);
|
public Task<IEnumerable<Venue>> GetFilteredVenues(string? nameSearch = null, string[]? tags = null, string[]? categories = null);
|
||||||
}
|
}
|
@ -30,7 +30,10 @@ public class VenueService : IVenueService
|
|||||||
.SingleOrDefaultAsync(v => v.Id == venueId);
|
.SingleOrDefaultAsync(v => v.Id == venueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Venue>> GetFilteredVenues(string[]? tags = null, string[]? categories = null)
|
public async Task<IEnumerable<Venue>> GetFilteredVenues(
|
||||||
|
string? nameSearch = null,
|
||||||
|
string[]? tags = null,
|
||||||
|
string[]? categories = null)
|
||||||
{
|
{
|
||||||
var venues = _context.Venues
|
var venues = _context.Venues
|
||||||
.Include(v => v.Category)
|
.Include(v => v.Category)
|
||||||
@ -39,9 +42,21 @@ public class VenueService : IVenueService
|
|||||||
|
|
||||||
IQueryable<Venue> filteredVenues = venues;
|
IQueryable<Venue> filteredVenues = venues;
|
||||||
|
|
||||||
|
if (nameSearch != null)
|
||||||
|
{
|
||||||
|
filteredVenues = filteredVenues.Where(v => v.Name.ToLower().Contains(nameSearch.ToLower()));
|
||||||
|
}
|
||||||
|
|
||||||
if (categories is { Length: > 0 })
|
if (categories is { Length: > 0 })
|
||||||
{
|
{
|
||||||
filteredVenues = venues.Where(v => categories.Contains(v.Category.Name));
|
filteredVenues = filteredVenues.Where(v => categories.Contains(v.Category.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags is { Length: > 0 })
|
||||||
|
{
|
||||||
|
filteredVenues = tags.Aggregate(filteredVenues, (current, filteredTag) =>
|
||||||
|
current.Where(v =>
|
||||||
|
v.Tags.Contains(_context.Tags.FirstOrDefault(t => t.Name == filteredTag)!)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return await filteredVenues.ToListAsync();
|
return await filteredVenues.ToListAsync();
|
||||||
|
@ -19,7 +19,7 @@ var app = builder.Build();
|
|||||||
|
|
||||||
#if LBQ_SEED_DATA
|
#if LBQ_SEED_DATA
|
||||||
var dbContext = app.Services.GetRequiredService<LeedsBeerQuestDbContext>();
|
var dbContext = app.Services.GetRequiredService<LeedsBeerQuestDbContext>();
|
||||||
var seeder = new LeedsBeerQuestSeeder(dbContext!, "");
|
var seeder = new LeedsBeerQuestSeeder(dbContext!, app.Configuration["Database:Seed:SeedDataLocation"]);
|
||||||
seeder.Seed();
|
seeder.Seed();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -4,5 +4,10 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Database": {
|
||||||
|
"Seed": {
|
||||||
|
"SeedDataLocation": "../leedsbeerquest.csv"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ export function VenueGrid(props: TVenueGridProps) {
|
|||||||
<TableCell align="left">Category</TableCell>
|
<TableCell align="left">Category</TableCell>
|
||||||
<TableCell align="left">Date Visited</TableCell>
|
<TableCell align="left">Date Visited</TableCell>
|
||||||
<TableCell align="left">Excerpt</TableCell>
|
<TableCell align="left">Excerpt</TableCell>
|
||||||
|
<TableCell align="left">Tags</TableCell>
|
||||||
<TableCell align="left">Phone</TableCell>
|
<TableCell align="left">Phone</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@ -32,6 +33,7 @@ export function VenueGrid(props: TVenueGridProps) {
|
|||||||
<TableCell align="left">{venue.category.name}</TableCell>
|
<TableCell align="left">{venue.category.name}</TableCell>
|
||||||
<TableCell align="left">{venue.dateAttended.toString()}</TableCell>
|
<TableCell align="left">{venue.dateAttended.toString()}</TableCell>
|
||||||
<TableCell align="left">{venue.excerpt}</TableCell>
|
<TableCell align="left">{venue.excerpt}</TableCell>
|
||||||
|
<TableCell align="left">{venue.tags.map(t => `${t.name} `)}</TableCell>
|
||||||
<TableCell align="left">{venue.phone}</TableCell>
|
<TableCell align="left">{venue.phone}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
@ -5,9 +5,13 @@ export class API {
|
|||||||
constructor(private baseAPIUrl: string) {}
|
constructor(private baseAPIUrl: string) {}
|
||||||
|
|
||||||
getVenues = async (venueFilters: TVenueFilters | null): Promise<TVenue[]> => {
|
getVenues = async (venueFilters: TVenueFilters | null): Promise<TVenue[]> => {
|
||||||
const categoriesParams = ArrayToArrayUrlParams("categories", venueFilters?.categories.map((c) => c.name) ?? []);
|
let params = "";
|
||||||
const response = await fetch(`${this.baseAPIUrl}/venue${categoriesParams}`);
|
params += ArrayToArrayUrlParams("categories", venueFilters?.categories.map((c) => c.name) ?? []);
|
||||||
console.log(`calling ${this.baseAPIUrl}/venue${categoriesParams}`);
|
params += ArrayToArrayUrlParams("tags", venueFilters?.tags.map((v) => v.name) ?? [], params.length < 1);
|
||||||
|
params += venueFilters?.nameFilter ? `${(params.length < 1 ? "?" : "&")}search=${venueFilters.nameFilter}` : "";
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseAPIUrl}/venue${params}`);
|
||||||
|
console.log(`calling ${this.baseAPIUrl}/venue${params}`);
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ export type TVenue = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TVenueFilters = {
|
export type TVenueFilters = {
|
||||||
|
nameFilter: string | null;
|
||||||
tags: TTag[];
|
tags: TTag[];
|
||||||
categories: TCategory[];
|
categories: TCategory[];
|
||||||
}
|
}
|
@ -1,34 +1,40 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { TextField } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { ChangeEventHandler, useState } from "react";
|
||||||
import { DropdownFilter } from "../components/dropdown-filter";
|
import { DropdownFilter } from "../components/dropdown-filter";
|
||||||
import { VenueGrid } from "../components/venue-grid";
|
import { VenueGrid } from "../components/venue-grid";
|
||||||
import { API } from "../lib/api";
|
import { API } from "../lib/api";
|
||||||
import { Environment } from "../lib/environment";
|
import { Environment } from "../lib/environment";
|
||||||
import { TCategory, TTag, TVenue, TVenueFilters } from "../lib/types";
|
import { TCategory, TTag, TVenue } from "../lib/types";
|
||||||
|
|
||||||
export function Index() {
|
export function Index() {
|
||||||
|
const [nameTextFilter, setNameTextFilter] = useState<string | null>(null);
|
||||||
const [filteredTags, setFilteredTags] = useState<TTag[]>([]);
|
const [filteredTags, setFilteredTags] = useState<TTag[]>([]);
|
||||||
const [filteredCategories, setFilteredCategories] = useState<TCategory[]>([]);
|
const [filteredCategories, setFilteredCategories] = useState<TCategory[]>([]);
|
||||||
|
|
||||||
const api = new API(Environment.BaseAPIUrl);
|
const api = new API(Environment.BaseAPIUrl);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const { data: venueData } = useQuery<TVenue[], Error>(["venues", filteredCategories, filteredTags, nameTextFilter], () => api.getVenues({nameFilter: nameTextFilter, categories: filteredCategories, tags: filteredTags}));
|
||||||
|
|
||||||
const { data: venueData } = useQuery<TVenue[], Error>(["venues", filteredCategories], () => api.getVenues({categories: filteredCategories, tags: filteredTags}));
|
|
||||||
|
|
||||||
const { data: categoryData } = useQuery<TCategory[], Error>(["categories"], api.getCategories);
|
const { data: categoryData } = useQuery<TCategory[], Error>(["categories"], api.getCategories);
|
||||||
|
|
||||||
const { data: tagData } = useQuery<TTag[], Error>(["tags"], api.getTags);
|
const { data: tagData } = useQuery<TTag[], Error>(["tags"], api.getTags);
|
||||||
|
|
||||||
// useEffect(() => {
|
const handleSearchChange = (event: any) => {
|
||||||
// console.log("useeffect called!");
|
const { value } = event.target;
|
||||||
// queryClient.invalidateQueries(["venues"]);
|
|
||||||
// }, [filteredCategories]);
|
if (value && value.length === 0) {
|
||||||
|
setNameTextFilter(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setNameTextFilter(value as string);
|
||||||
|
}
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
<h1>Leeds Beer Quest</h1>
|
<h1>Leeds Beer Quest</h1>
|
||||||
<div style={{ display: "flex", justifyContent: "space-around" }}>
|
<div style={{ display: "flex", justifyContent: "space-around" }}>
|
||||||
|
<TextField label={"Name"} variant="outlined" onChange={handleSearchChange} />
|
||||||
<DropdownFilter filters={tagData ?? []} setSelectedFilters={setFilteredTags} prefillText={"Tags"} />
|
<DropdownFilter filters={tagData ?? []} setSelectedFilters={setFilteredTags} prefillText={"Tags"} />
|
||||||
<DropdownFilter filters={categoryData ?? []} setSelectedFilters={setFilteredCategories} prefillText={"Categories"} />
|
<DropdownFilter filters={categoryData ?? []} setSelectedFilters={setFilteredCategories} prefillText={"Categories"} />
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user