Remove server-side search filtering and implement client-side filtering
This commit moves project search filtering from the server to the client. Changes include: - Remove `Query` field from `ListTablosInput` and related handlers - Add French date formatting for project cards - Convert search form to client-side filter with data attributes - Add empty state message for no search results - Update button border-radius from 0 to 0.7rem - Increase air.toml build command to include Tailwind CSS generation
This commit is contained in:
parent
8bcf81a3f1
commit
c780dd1625
14 changed files with 478 additions and 395 deletions
|
|
@ -4,11 +4,11 @@ root = "."
|
|||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
cmd = "go run github.com/a-h/templ/cmd/templ@latest generate && go run github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1 generate && go build -o ./tmp/main ."
|
||||
cmd = "go run ./cmd/buildstyles && pnpm exec tailwindcss -i tailwind.input.css -o static/tailwind.css --cwd . && go run github.com/a-h/templ/cmd/templ@v0.3.1020 generate && go run github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1 generate && go build -o ./tmp/main ."
|
||||
entrypoint = ["./tmp/main"]
|
||||
include_ext = ["go", "templ", "sql", "css", "html", "png", "svg", "webmanifest", "json"]
|
||||
exclude_dir = ["tmp", "vendor", ".git", "internal/db/sqlc"]
|
||||
exclude_regex = ["_templ\\.go$"]
|
||||
exclude_regex = ["_templ\\.go$", "static/(styles|tailwind)\\.css$"]
|
||||
delay = 200
|
||||
stop_on_error = true
|
||||
send_interrupt = true
|
||||
|
|
|
|||
|
|
@ -164,7 +164,6 @@ func (r *PostgresAuthRepository) CreateTablo(ctx context.Context, input tablomod
|
|||
func (r *PostgresAuthRepository) ListTablos(ctx context.Context, input tablomodel.ListInput) ([]tablomodel.Record, error) {
|
||||
params := sqlcdb.ListTablosParams{
|
||||
OwnerID: input.OwnerID,
|
||||
Query: nullableText(strings.TrimSpace(input.Query)),
|
||||
Status: nullableStatus(input.Status),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,5 @@ type UpdateInput struct {
|
|||
|
||||
type ListInput struct {
|
||||
OwnerID uuid.UUID
|
||||
Query string
|
||||
Status *Status
|
||||
}
|
||||
|
|
|
|||
13
go-backend/internal/web/dates/french.go
Normal file
13
go-backend/internal/web/dates/french.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package dates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var frenchMonths = [...]string{"janv.", "fevr.", "mars", "avr.", "mai", "juin", "juil.", "aout", "sept.", "oct.", "nov.", "dec."}
|
||||
|
||||
func FormatFrenchDate(value time.Time) string {
|
||||
month := frenchMonths[int(value.Month())-1]
|
||||
return fmt.Sprintf("%02d %s %d", value.Day(), month, value.Year())
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
tablomodel "xtablo-backend/internal/tablos"
|
||||
"xtablo-backend/internal/web/dates"
|
||||
"xtablo-backend/internal/web/views"
|
||||
)
|
||||
|
||||
|
|
@ -37,16 +38,11 @@ type ListTablosInput = tablomodel.ListInput
|
|||
|
||||
type TablosPageState struct {
|
||||
View string
|
||||
Query string
|
||||
Status string
|
||||
ModalKind string
|
||||
EditingTabloID string
|
||||
}
|
||||
|
||||
func normalizeTabloQuery(query string) string {
|
||||
return strings.ToLower(strings.TrimSpace(query))
|
||||
}
|
||||
|
||||
func parseTablosPageState(values interface {
|
||||
Get(string) string
|
||||
}) TablosPageState {
|
||||
|
|
@ -64,7 +60,6 @@ func parseTablosPageState(values interface {
|
|||
|
||||
return TablosPageState{
|
||||
View: view,
|
||||
Query: strings.TrimSpace(values.Get("q")),
|
||||
Status: status,
|
||||
ModalKind: normalizedModalKind(strings.TrimSpace(values.Get("modal"))),
|
||||
}
|
||||
|
|
@ -329,7 +324,6 @@ func tablosPageViewModel(user PublicUser, state TablosPageState, tablos []TabloR
|
|||
return views.NewTablosPageViewModel(
|
||||
user.DisplayName,
|
||||
state.View,
|
||||
state.Query,
|
||||
state.Status,
|
||||
state.ModalKind,
|
||||
state.EditingTabloID,
|
||||
|
|
@ -343,7 +337,6 @@ func tablosPageViewModel(user PublicUser, state TablosPageState, tablos []TabloR
|
|||
func listTablosForState(ctx context.Context, repo AuthRepository, ownerID uuid.UUID, state TablosPageState) ([]TabloRecord, error) {
|
||||
return repo.ListTablos(ctx, ListTablosInput{
|
||||
OwnerID: ownerID,
|
||||
Query: state.Query,
|
||||
Status: state.statusFilter(),
|
||||
})
|
||||
}
|
||||
|
|
@ -396,7 +389,6 @@ func (r *InMemoryAuthRepository) ListTablos(_ context.Context, input ListTablosI
|
|||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
query := normalizeTabloQuery(input.Query)
|
||||
var tablos []TabloRecord
|
||||
|
||||
for _, tablo := range r.tablos {
|
||||
|
|
@ -409,9 +401,6 @@ func (r *InMemoryAuthRepository) ListTablos(_ context.Context, input ListTablosI
|
|||
if input.Status != nil && tablo.Status != *input.Status {
|
||||
continue
|
||||
}
|
||||
if query != "" && !strings.Contains(strings.ToLower(tablo.Name), query) {
|
||||
continue
|
||||
}
|
||||
|
||||
tablos = append(tablos, tablo)
|
||||
}
|
||||
|
|
@ -505,9 +494,6 @@ func buildStatefulRequestURL(path string, state TablosPageState) string {
|
|||
values := url.Values{}
|
||||
values.Set("view", state.View)
|
||||
values.Set("status", state.Status)
|
||||
if strings.TrimSpace(state.Query) != "" {
|
||||
values.Set("q", strings.TrimSpace(state.Query))
|
||||
}
|
||||
encoded := values.Encode()
|
||||
if encoded == "" {
|
||||
return path
|
||||
|
|
@ -538,13 +524,11 @@ func tabloIconPresentation(name string) (string, string, string, string) {
|
|||
}
|
||||
|
||||
func formatFrenchDate(value time.Time) string {
|
||||
months := []string{"janv.", "fevr.", "mars", "avr.", "mai", "juin", "juil.", "aout", "sept.", "oct.", "nov.", "dec."}
|
||||
month := months[int(value.Month())-1]
|
||||
return fmt.Sprintf("%02d %s %d", value.Day(), month, value.Year())
|
||||
return dates.FormatFrenchDate(value)
|
||||
}
|
||||
|
||||
func formatCardDate(value time.Time) string {
|
||||
return value.Format("Jan 02, 2006")
|
||||
return dates.FormatFrenchDate(value)
|
||||
}
|
||||
|
||||
func projectInitial(name string) string {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInMemoryTablosListExcludesSoftDeletedRows(t *testing.T) {
|
||||
|
|
@ -54,7 +55,7 @@ func TestInMemoryTablosListExcludesSoftDeletedRows(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInMemoryTablosListFiltersBySearchAndStatus(t *testing.T) {
|
||||
func TestInMemoryTablosListFiltersByStatus(t *testing.T) {
|
||||
repo := NewInMemoryAuthRepository()
|
||||
user, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
|
||||
if err != nil {
|
||||
|
|
@ -81,15 +82,14 @@ func TestInMemoryTablosListFiltersBySearchAndStatus(t *testing.T) {
|
|||
|
||||
tablos, err := repo.ListTablos(context.Background(), ListTablosInput{
|
||||
OwnerID: user.ID,
|
||||
Query: "delivery",
|
||||
Status: &[]TabloStatus{TabloStatusInProgress}[0],
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list filtered tablos: %v", err)
|
||||
t.Fatalf("list status-filtered tablos: %v", err)
|
||||
}
|
||||
|
||||
if len(tablos) != 1 {
|
||||
t.Fatalf("expected 1 filtered tablo, got %d", len(tablos))
|
||||
t.Fatalf("expected 1 status-filtered tablo, got %d", len(tablos))
|
||||
}
|
||||
|
||||
if tablos[0].ID != expectedTablo.ID {
|
||||
|
|
@ -177,7 +177,7 @@ func TestGetTablosPageDefaultsToGridAndAllStatus(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetTablosPageHonorsSearchAndStatus(t *testing.T) {
|
||||
func TestGetTablosPageIgnoresSearchQueryParamAndLeavesFilteringToClient(t *testing.T) {
|
||||
repo := NewInMemoryAuthRepository()
|
||||
handler := NewAuthHandler(repo)
|
||||
sessionCookie := loginTestUser(t, handler, "demo@xtablo.com", "xtablo-demo")
|
||||
|
|
@ -192,10 +192,10 @@ func TestGetTablosPageHonorsSearchAndStatus(t *testing.T) {
|
|||
_, err := repo.CreateTablo(context.Background(), CreateTabloInput{
|
||||
OwnerID: userID,
|
||||
Name: "Alpha Draft",
|
||||
Status: TabloStatusTodo,
|
||||
Status: TabloStatusInProgress,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create todo tablo: %v", err)
|
||||
t.Fatalf("create first in-progress tablo: %v", err)
|
||||
}
|
||||
|
||||
_, err = repo.CreateTablo(context.Background(), CreateTabloInput{
|
||||
|
|
@ -218,11 +218,54 @@ func TestGetTablosPageHonorsSearchAndStatus(t *testing.T) {
|
|||
}
|
||||
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "Beta Delivery") {
|
||||
t.Fatalf("expected filtered tablo to be visible, got %q", body)
|
||||
if !strings.Contains(body, "Alpha Draft") {
|
||||
t.Fatalf("expected non-matching tablo to stay visible for client filtering, got %q", body)
|
||||
}
|
||||
if strings.Contains(body, "Alpha Draft") {
|
||||
t.Fatalf("expected non-matching tablo to be filtered out, got %q", body)
|
||||
if !strings.Contains(body, "Beta Delivery") {
|
||||
t.Fatalf("expected matching tablo to be visible, got %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTablosPageRendersClientSideProjectFilterHooks(t *testing.T) {
|
||||
repo := NewInMemoryAuthRepository()
|
||||
handler := NewAuthHandler(repo)
|
||||
sessionCookie := loginTestUser(t, handler, "demo@xtablo.com", "xtablo-demo")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.AddCookie(sessionCookie)
|
||||
userID, ok := handler.currentUserID(req.Context(), req)
|
||||
if !ok {
|
||||
t.Fatal("expected user session")
|
||||
}
|
||||
|
||||
_, err := repo.CreateTablo(context.Background(), CreateTabloInput{
|
||||
OwnerID: userID,
|
||||
Name: "Searchable Project",
|
||||
Status: TabloStatusTodo,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create searchable tablo: %v", err)
|
||||
}
|
||||
|
||||
pageReq := httptest.NewRequest(http.MethodGet, "/tablos", nil)
|
||||
pageReq.AddCookie(sessionCookie)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.GetTablosPage().ServeHTTP(rec, pageReq)
|
||||
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{
|
||||
`data-project-filter-root`,
|
||||
`data-project-filter-input`,
|
||||
`data-project-filter-item`,
|
||||
`window.xtabloProjectFilterInitialized`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("expected client-side filter markup %q, got %q", want, body)
|
||||
}
|
||||
}
|
||||
if strings.Contains(body, `name="q"`) {
|
||||
t.Fatalf("expected search query field to be removed from markup, got %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,6 +486,13 @@ func TestGetTablosPageGridUsesProjectDateRowMarkup(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFormatCardDateUsesFrenchMonthNames(t *testing.T) {
|
||||
got := formatCardDate(time.Date(2026, time.May, 10, 9, 0, 0, 0, time.UTC))
|
||||
if got != "10 mai 2026" {
|
||||
t.Fatalf("expected French card date label, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTablosPageGridUsesProjectCardMarkup(t *testing.T) {
|
||||
repo := NewInMemoryAuthRepository()
|
||||
handler := NewAuthHandler(repo)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.ui-button {
|
||||
align-items: center;
|
||||
border: 0;
|
||||
border-radius: 0rem;
|
||||
border-radius: 0.7rem;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ func TestOverviewProjectsFromTablosCarriesColorAndEditURL(t *testing.T) {
|
|||
if project.EditRequestURL != "/tablos/11111111-1111-1111-1111-111111111111/edit" {
|
||||
t.Fatalf("expected edit request url to be set, got %q", project.EditRequestURL)
|
||||
}
|
||||
if project.CardDateLabel != "10 mai 2026" {
|
||||
t.Fatalf("expected French card date label, got %q", project.CardDateLabel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverviewProjectsSectionRendersColorAndEditAction(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/a-h/templ"
|
||||
tablomodel "xtablo-backend/internal/tablos"
|
||||
"xtablo-backend/internal/web/dates"
|
||||
)
|
||||
|
||||
const overviewProjectsPreviewLimit = 6
|
||||
|
|
@ -119,7 +120,7 @@ func OverviewProjectsFromTablos(tablos []tablomodel.Record) []TabloCardView {
|
|||
StatusTone: statusTone,
|
||||
Initial: projectInitial(tablo.Name),
|
||||
Accent: overviewProjectAccent(tablo.Name),
|
||||
CardDateLabel: tablo.CreatedAt.Format("Jan 02, 2006"),
|
||||
CardDateLabel: dates.FormatFrenchDate(tablo.CreatedAt),
|
||||
Progress: progress,
|
||||
ProgressLabel: progressPercentLabel(progress),
|
||||
DeleteRequestURL: "/tablos/" + tablo.ID.String(),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package views
|
|||
import "xtablo-backend/internal/web/ui"
|
||||
|
||||
templ TablosPageContent(vm TablosPageViewModel) {
|
||||
<div class="px-4 pt-8 pb-6">
|
||||
<div class="px-4 pt-8 pb-6" data-project-filter-root>
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-8 gap-4">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Mes Projets</h1>
|
||||
@ui.Button(ui.ButtonProps{
|
||||
|
|
@ -49,27 +49,18 @@ templ TablosPageContent(vm TablosPageViewModel) {
|
|||
</a>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-4 mb-6">
|
||||
<form
|
||||
class="relative md:w-[350px]"
|
||||
hx-get={ vm.SearchHref() }
|
||||
hx-target="#app-main-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
hx-trigger="input changed delay:300ms from:input[name='q']"
|
||||
>
|
||||
<input type="hidden" name="view" value={ vm.View }/>
|
||||
<input type="hidden" name="status" value={ vm.Status }/>
|
||||
<div class="relative md:w-[350px]">
|
||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5 pointer-events-none">
|
||||
@ActionIcon("search")
|
||||
</span>
|
||||
<input
|
||||
name="q"
|
||||
value={ vm.Query }
|
||||
data-project-filter-input
|
||||
placeholder="Rechercher..."
|
||||
autocomplete="off"
|
||||
class="w-full pl-10 pr-4 py-3 border border-[#EAECF0] dark:border-gray-700 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-purple-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400"
|
||||
type="text"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
@StatusPill(vm, "all", "Tous")
|
||||
@StatusPill(vm, "todo", "Pas commencé")
|
||||
|
|
@ -79,19 +70,27 @@ templ TablosPageContent(vm TablosPageViewModel) {
|
|||
</div>
|
||||
if vm.HasTablos() {
|
||||
if vm.IsGridView() {
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6" data-project-filter-results>
|
||||
for _, tablo := range vm.Tablos {
|
||||
@TabloGridCard(tablo)
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl border border-[#EAECF0] dark:border-gray-700 overflow-x-auto -mx-4 sm:mx-0">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl border border-[#EAECF0] dark:border-gray-700 overflow-x-auto -mx-4 sm:mx-0" data-project-filter-results>
|
||||
@ui.Table(ui.TableProps{
|
||||
Head: TabloListHead(),
|
||||
Body: TabloListBody(vm.Tablos),
|
||||
})
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
data-project-filter-empty
|
||||
hidden
|
||||
class="rounded-xl border border-dashed border-[#EAECF0] bg-white/80 px-6 py-10 text-center text-sm text-gray-500 dark:border-gray-700 dark:bg-gray-800/60 dark:text-gray-400"
|
||||
>
|
||||
Aucun projet ne correspond à votre recherche.
|
||||
</div>
|
||||
@InitProjectFilterScript()
|
||||
} else {
|
||||
@ui.EmptyState(ui.EmptyStateProps{
|
||||
Title: "Aucun projet trouvé",
|
||||
|
|
@ -177,7 +176,7 @@ templ TabloGridCard(tablo TabloCardView) {
|
|||
}
|
||||
|
||||
templ TabloGridCardWithAttrs(tablo TabloCardView, attrs templ.Attributes) {
|
||||
<article class="project-card" style={ projectColorVariableStyle(tablo.Color) } { attrs... }>
|
||||
<article class="project-card" style={ projectColorVariableStyle(tablo.Color) } data-project-filter-item data-project-name={ tablo.Name } { attrs... }>
|
||||
<div class="project-card-top">
|
||||
@ui.Badge(ui.BadgeProps{
|
||||
Label: tablo.StatusLabel,
|
||||
|
|
@ -211,7 +210,7 @@ templ TabloGridCardWithAttrs(tablo TabloCardView, attrs templ.Attributes) {
|
|||
}
|
||||
|
||||
templ TabloListRow(tablo TabloCardView) {
|
||||
<tr class="border-t border-[#EAECF0] dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer" style={ projectColorVariableStyle(tablo.Color) }>
|
||||
<tr class="border-t border-[#EAECF0] dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer" style={ projectColorVariableStyle(tablo.Color) } data-project-filter-item data-project-name={ tablo.Name }>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="project-list-icon w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden [&>svg]:w-4 [&>svg]:h-4">
|
||||
|
|
@ -256,6 +255,56 @@ templ CreateTabloModal(vm TablosPageViewModel) {
|
|||
})
|
||||
}
|
||||
|
||||
templ InitProjectFilterScript() {
|
||||
<script>
|
||||
(function () {
|
||||
function normalizeProjectFilterValue(value) {
|
||||
return (value || "").toLowerCase().trim();
|
||||
}
|
||||
function applyProjectFilter(root) {
|
||||
if (!root) return;
|
||||
var input = root.querySelector("[data-project-filter-input]");
|
||||
var results = root.querySelector("[data-project-filter-results]");
|
||||
var empty = root.querySelector("[data-project-filter-empty]");
|
||||
if (!input || !results || !empty) return;
|
||||
var query = normalizeProjectFilterValue(input.value);
|
||||
var visibleCount = 0;
|
||||
root.querySelectorAll("[data-project-filter-item]").forEach(function (item) {
|
||||
var name = normalizeProjectFilterValue(item.getAttribute("data-project-name") || item.textContent);
|
||||
var matches = query === "" || name.indexOf(query) !== -1;
|
||||
item.hidden = !matches;
|
||||
if (matches) {
|
||||
item.removeAttribute("hidden");
|
||||
visibleCount += 1;
|
||||
} else {
|
||||
item.setAttribute("hidden", "");
|
||||
}
|
||||
});
|
||||
var hasVisibleResults = visibleCount > 0;
|
||||
results.hidden = !hasVisibleResults;
|
||||
empty.hidden = hasVisibleResults;
|
||||
if (hasVisibleResults) {
|
||||
results.removeAttribute("hidden");
|
||||
empty.setAttribute("hidden", "");
|
||||
} else {
|
||||
results.setAttribute("hidden", "");
|
||||
empty.removeAttribute("hidden");
|
||||
}
|
||||
}
|
||||
function handleProjectFilterInput(event) {
|
||||
var input = event.target.closest("[data-project-filter-input]");
|
||||
if (!input) return;
|
||||
applyProjectFilter(input.closest("[data-project-filter-root]"));
|
||||
}
|
||||
if (!window.xtabloProjectFilterInitialized) {
|
||||
document.addEventListener("input", handleProjectFilterInput);
|
||||
window.xtabloProjectFilterInitialized = true;
|
||||
}
|
||||
document.querySelectorAll("[data-project-filter-root]").forEach(applyProjectFilter);
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
templ TabloListHead() {
|
||||
<tr class="bg-gray-50 dark:bg-gray-800/80 border-b border-[#EAECF0] dark:border-gray-700">
|
||||
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">Projet</th>
|
||||
|
|
@ -281,7 +330,6 @@ templ CreateTabloModalBody(vm TablosPageViewModel) {
|
|||
>
|
||||
<input type="hidden" name="view" value={ vm.View }/>
|
||||
<input type="hidden" name="status" value={ vm.Status }/>
|
||||
<input type="hidden" name="q" value={ vm.Query }/>
|
||||
<input type="hidden" name="modal" value="create"/>
|
||||
if vm.ErrorMessage != "" {
|
||||
<div class="mb-1 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{ vm.ErrorMessage }</div>
|
||||
|
|
@ -373,7 +421,6 @@ templ EditTabloModalBody(vm TablosPageViewModel) {
|
|||
>
|
||||
<input type="hidden" name="view" value={ vm.View }/>
|
||||
<input type="hidden" name="status" value={ vm.Status }/>
|
||||
<input type="hidden" name="q" value={ vm.Query }/>
|
||||
if vm.ErrorMessage != "" {
|
||||
<div class="mb-1 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{ vm.ErrorMessage }</div>
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,6 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
|
|
@ -34,7 +33,6 @@ type TabloCardView struct {
|
|||
type TablosPageViewModel struct {
|
||||
DisplayName string
|
||||
View string
|
||||
Query string
|
||||
Status string
|
||||
ModalKind string
|
||||
EditingTabloID string
|
||||
|
|
@ -44,11 +42,10 @@ type TablosPageViewModel struct {
|
|||
Tablos []TabloCardView
|
||||
}
|
||||
|
||||
func NewTablosPageViewModel(displayName string, view string, query string, status string, modalKind string, editingTabloID string, formName string, formColor string, errorMessage string, tablos []TabloCardView) TablosPageViewModel {
|
||||
func NewTablosPageViewModel(displayName string, view string, status string, modalKind string, editingTabloID string, formName string, formColor string, errorMessage string, tablos []TabloCardView) TablosPageViewModel {
|
||||
return TablosPageViewModel{
|
||||
DisplayName: displayName,
|
||||
View: normalizedView(view),
|
||||
Query: strings.TrimSpace(query),
|
||||
Status: normalizedStatus(status),
|
||||
ModalKind: normalizedModalKind(modalKind),
|
||||
EditingTabloID: strings.TrimSpace(editingTabloID),
|
||||
|
|
@ -91,22 +88,6 @@ func (vm TablosPageViewModel) ViewHref(view string) string {
|
|||
return "/tablos?" + values.Encode()
|
||||
}
|
||||
|
||||
func (vm TablosPageViewModel) SearchHref() string {
|
||||
return "/tablos"
|
||||
}
|
||||
|
||||
func (vm TablosPageViewModel) HiddenStateFields() map[string]string {
|
||||
return map[string]string{
|
||||
"view": vm.View,
|
||||
"status": vm.Status,
|
||||
"q": vm.Query,
|
||||
}
|
||||
}
|
||||
|
||||
func (vm TablosPageViewModel) SearchValues() string {
|
||||
return fmt.Sprintf("view=%s&status=%s", vm.View, vm.Status)
|
||||
}
|
||||
|
||||
func (vm TablosPageViewModel) CreateModalHref() string {
|
||||
values := vm.baseValues()
|
||||
values.Set("modal", "create")
|
||||
|
|
@ -127,10 +108,6 @@ func (vm TablosPageViewModel) EditSubmitHref() string {
|
|||
return "/tablos/" + vm.EditingTabloID
|
||||
}
|
||||
|
||||
func (vm TablosPageViewModel) HasSearch() bool {
|
||||
return vm.Query != ""
|
||||
}
|
||||
|
||||
func normalizedView(view string) string {
|
||||
if view == "list" {
|
||||
return "list"
|
||||
|
|
@ -171,9 +148,6 @@ func (vm TablosPageViewModel) baseValues() url.Values {
|
|||
values := url.Values{}
|
||||
values.Set("view", vm.View)
|
||||
values.Set("status", vm.Status)
|
||||
if vm.Query != "" {
|
||||
values.Set("q", vm.Query)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ input {
|
|||
.ui-button {
|
||||
align-items: center;
|
||||
border: 0;
|
||||
border-radius: 0rem;
|
||||
border-radius: 0.7rem;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -264,6 +264,10 @@
|
|||
border-bottom-style: var(--tw-border-style);
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
.border-dashed {
|
||||
--tw-border-style: dashed;
|
||||
border-style: dashed;
|
||||
}
|
||||
.border-\[\#DB9729\] {
|
||||
border-color: #DB9729;
|
||||
}
|
||||
|
|
@ -321,6 +325,14 @@
|
|||
.bg-white {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
.bg-white\/80 {
|
||||
background-color: color-mix(in srgb, #fff 80%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
& {
|
||||
background-color: color-mix(in oklab, var(--color-white) 80%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
.px-4 {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
|
|
@ -336,6 +348,9 @@
|
|||
.py-4 {
|
||||
padding-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.py-10 {
|
||||
padding-block: calc(var(--spacing) * 10);
|
||||
}
|
||||
.pt-8 {
|
||||
padding-top: calc(var(--spacing) * 8);
|
||||
}
|
||||
|
|
@ -351,6 +366,9 @@
|
|||
.pl-10 {
|
||||
padding-left: calc(var(--spacing) * 10);
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
@ -558,6 +576,16 @@
|
|||
background-color: var(--color-gray-800);
|
||||
}
|
||||
}
|
||||
.dark\:bg-gray-800\/60 {
|
||||
&:is(.dark *) {
|
||||
background-color: color-mix(in srgb, oklch(27.8% 0.033 256.848) 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
& {
|
||||
background-color: color-mix(in oklab, var(--color-gray-800) 60%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark\:bg-gray-800\/80 {
|
||||
&:is(.dark *) {
|
||||
background-color: color-mix(in srgb, oklch(27.8% 0.033 256.848) 80%, transparent);
|
||||
|
|
|
|||
Loading…
Reference in a new issue