+Show contents count in /baende list

This commit is contained in:
Simon Martens
2026-01-30 19:39:02 +01:00
parent e7e279aeeb
commit a70cdd6488
9 changed files with 100 additions and 19 deletions

View File

@@ -57,6 +57,7 @@ type BaendeCache struct {
EntriesAgents map[string][]*dbmodels.REntriesAgents EntriesAgents map[string][]*dbmodels.REntriesAgents
Items map[string][]*dbmodels.Item Items map[string][]*dbmodels.Item
Users map[string]*dbmodels.User Users map[string]*dbmodels.User
ContentsCount map[string]int
CachedAt time.Time CachedAt time.Time
} }
@@ -93,6 +94,9 @@ func (bc *BaendeCache) GetUsers() interface{} {
return bc.Users return bc.Users
} }
func (bc *BaendeCache) GetContentsCount() interface{} {
return bc.ContentsCount
}
const ( const (
TEST_SUPERUSER_MAIL = "demo@example.com" TEST_SUPERUSER_MAIL = "demo@example.com"
TEST_SUPERUSER_PASS = "password" TEST_SUPERUSER_PASS = "password"
@@ -671,6 +675,16 @@ func (app *App) EnsureBaendeCache() (*BaendeCache, error) {
} }
} }
// Load contents counts
contentsCount := map[string]int{}
if len(entryIDs) > 0 {
counts, err := dbmodels.CountContentsEntries(app.PB.App, entryIDs)
if err != nil {
return nil, err
}
contentsCount = counts
}
// Load users (editors) // Load users (editors)
usersMap := map[string]*dbmodels.User{} usersMap := map[string]*dbmodels.User{}
editorIDs := map[string]struct{}{} editorIDs := map[string]struct{}{}
@@ -704,6 +718,7 @@ func (app *App) EnsureBaendeCache() (*BaendeCache, error) {
EntriesAgents: entryAgentsMap, EntriesAgents: entryAgentsMap,
Items: itemsMap, Items: itemsMap,
Users: usersMap, Users: usersMap,
ContentsCount: contentsCount,
CachedAt: time.Now(), CachedAt: time.Now(),
} }

View File

@@ -27,7 +27,7 @@ const (
URL_BAENDE_DELETE = "/baende/delete-info/{id}" URL_BAENDE_DELETE = "/baende/delete-info/{id}"
TEMPLATE_BAENDE = "/baende/" TEMPLATE_BAENDE = "/baende/"
URL_BAENDE_DETAILS = "/baende/details/{id}" URL_BAENDE_DETAILS = "/baende/details/{id}"
BAENDE_PAGE_SIZE = 150 BAENDE_PAGE_SIZE = 100
) )
func init() { func init() {
@@ -55,6 +55,7 @@ type BaendeResult struct {
EntriesAgents map[string][]*dbmodels.REntriesAgents EntriesAgents map[string][]*dbmodels.REntriesAgents
Items map[string][]*dbmodels.Item Items map[string][]*dbmodels.Item
Users map[string]*dbmodels.User Users map[string]*dbmodels.User
ContentsCount map[string]int
} }
type BaendeDetailsResult struct { type BaendeDetailsResult struct {
@@ -136,6 +137,15 @@ func (p *BaendePage) handleRow(engine *templating.Engine, app core.App) HandleFu
app.Logger().Error("Failed to get items for entry", "error", err) app.Logger().Error("Failed to get items for entry", "error", err)
} }
contents, err := dbmodels.Contents_Entry(app, entry.Id)
if err != nil {
app.Logger().Error("Failed to get contents for entry", "error", err)
}
contentsCount := 0
if contents != nil {
contentsCount = len(contents)
}
var editorUser *dbmodels.User var editorUser *dbmodels.User
if editorID := entry.Editor(); editorID != "" { if editorID := entry.Editor(); editorID != "" {
user, err := dbmodels.Users_ID(app, editorID) user, err := dbmodels.Users_ID(app, editorID)
@@ -147,11 +157,12 @@ func (p *BaendePage) handleRow(engine *templating.Engine, app core.App) HandleFu
} }
data := map[string]any{ data := map[string]any{
"entry": entry, "entry": entry,
"items": items, "items": items,
"editor_user": editorUser, "editor_user": editorUser,
"is_admin": req.IsAdmin(), "contents_count": contentsCount,
"csrf_token": req.Session().Token, "is_admin": req.IsAdmin(),
"csrf_token": req.Session().Token,
} }
return engine.Response200(e, "/baende/row/", data, "fragment") return engine.Response200(e, "/baende/row/", data, "fragment")
@@ -362,6 +373,11 @@ func (p *BaendePage) buildResultData(app core.App, ma pagemodels.IApp, e *core.R
return data, fmt.Errorf("failed to get users from cache") return data, fmt.Errorf("failed to get users from cache")
} }
contentsCount, ok := cacheInterface.GetContentsCount().(map[string]int)
if !ok {
return data, fmt.Errorf("failed to get contents count from cache")
}
// Apply search/letter/filters // Apply search/letter/filters
filteredEntries := allEntries filteredEntries := allEntries
if search != "" { if search != "" {
@@ -472,6 +488,7 @@ func (p *BaendePage) buildResultData(app core.App, ma pagemodels.IApp, e *core.R
EntriesAgents: entryAgentsMap, EntriesAgents: entryAgentsMap,
Items: itemsMap, Items: itemsMap,
Users: usersMap, Users: usersMap,
ContentsCount: contentsCount,
} }
data["offset"] = offset data["offset"] = offset
data["total_count"] = totalCount data["total_count"] = totalCount

View File

@@ -294,6 +294,31 @@ func Items_Entries(app core.App, ids []any) ([]*Item, error) {
return ret, err return ret, err
} }
type EntryCount struct {
Count int `db:"count"`
ID string `db:"id"`
}
func CountContentsEntries(app core.App, ids []any) (map[string]int, error) {
if len(ids) == 0 {
return map[string]int{}, nil
}
counts := []EntryCount{}
err := app.RecordQuery(CONTENTS_TABLE).
Select("count(*) as count, " + ENTRIES_TABLE + " as id").
Where(dbx.HashExp{ENTRIES_TABLE: ids}).
GroupBy(ENTRIES_TABLE).
All(&counts)
if err != nil {
return nil, err
}
ret := make(map[string]int, len(counts))
for _, c := range counts {
ret[c.ID] = c.Count
}
return ret, nil
}
func Contents_MusenalmID(app core.App, id string) (*Content, error) { func Contents_MusenalmID(app core.App, id string) (*Content, error) {
ret, err := TableByField[Content](app, CONTENTS_TABLE, MUSENALMID_FIELD, id) ret, err := TableByField[Content](app, CONTENTS_TABLE, MUSENALMID_FIELD, id)
return &ret, err return &ret, err

View File

@@ -18,6 +18,7 @@ type BaendeCacheInterface interface {
GetEntriesAgents() interface{} // Returns map[string][]*dbmodels.REntriesAgents GetEntriesAgents() interface{} // Returns map[string][]*dbmodels.REntriesAgents
GetItems() interface{} // Returns map[string][]*dbmodels.Item GetItems() interface{} // Returns map[string][]*dbmodels.Item
GetUsers() interface{} // Returns map[string]*dbmodels.User GetUsers() interface{} // Returns map[string]*dbmodels.User
GetContentsCount() interface{} // Returns map[string]int
} }
type IApp interface { type IApp interface {

View File

@@ -608,7 +608,7 @@ class="container-normal font-sans mt-10">
" "
:disabled="loading"> :disabled="loading">
<i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i> <i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i>
<span x-text="loading ? 'Lädt...' : 'Weitere 150 laden'"></span> <span x-text="loading ? 'Lädt...' : 'Weitere 100 laden'"></span>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -157,6 +157,14 @@
</a> </a>
<div class="data-tip">Bearbeiten</div> <div class="data-tip">Bearbeiten</div>
</tool-tip> </tool-tip>
<tool-tip position="top" class="inline">
<a href="/almanach/{{ $entry.MusenalmID }}/contents/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
<i class="ri-file-list-3-line"></i>
{{- $count := index $model.result.ContentsCount $entry.Id -}}
<span>{{ if $count }}{{ $count }}{{ else }}0{{ end }}</span>
</a>
<div class="data-tip">Beiträge bearbeiten</div>
</tool-tip>
<tool-tip position="top" class="inline"> <tool-tip position="top" class="inline">
<button <button
type="button" type="button"

View File

@@ -13,11 +13,11 @@
{{- if $entry.References -}} {{- if $entry.References -}}
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span> <span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span>
{{- end -}} {{- end -}}
<div class="flex flex-wrap items-center gap-1.5 pt-1"> <div class="flex flex-wrap items-center gap-1.5 pt-1">
<tool-tip position="top" class="inline"> <tool-tip position="top" class="inline">
<a href="/almanach/{{ $entry.MusenalmID }}" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900"> <a href="/almanach/{{ $entry.MusenalmID }}" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
<i class="ri-eye-line"></i> <i class="ri-eye-line"></i>
</a> </a>
<div class="data-tip">Ansehen</div> <div class="data-tip">Ansehen</div>
</tool-tip> </tool-tip>
{{- if (IsAdminOrEditor $model.request.user) -}} {{- if (IsAdminOrEditor $model.request.user) -}}
@@ -27,6 +27,14 @@
</a> </a>
<div class="data-tip">Bearbeiten</div> <div class="data-tip">Bearbeiten</div>
</tool-tip> </tool-tip>
<tool-tip position="top" class="inline">
<a href="/almanach/{{ $entry.MusenalmID }}/contents/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
<i class="ri-file-list-3-line"></i>
{{- $count := index $model.result.ContentsCount $entry.Id -}}
<span>{{ if $count }}{{ $count }}{{ else }}0{{ end }}</span>
</a>
<div class="data-tip">Beiträge bearbeiten</div>
</tool-tip>
<tool-tip position="top" class="inline"> <tool-tip position="top" class="inline">
<button <button
type="button" type="button"

View File

@@ -42,7 +42,7 @@
" "
:disabled="loading"> :disabled="loading">
<i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i> <i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i>
<span x-text="loading ? 'Lädt...' : 'Weitere 150 laden'"></span> <span x-text="loading ? 'Lädt...' : 'Weitere 100 laden'"></span>
</button> </button>
</div> </div>

View File

@@ -21,12 +21,19 @@
<div class="data-tip">Ansehen</div> <div class="data-tip">Ansehen</div>
</tool-tip> </tool-tip>
{{- if .is_admin -}} {{- if .is_admin -}}
<tool-tip position="top" class="inline"> <tool-tip position="top" class="inline">
<a href="/almanach/{{ $entry.MusenalmID }}/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900"> <a href="/almanach/{{ $entry.MusenalmID }}/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
<i class="ri-edit-line"></i> <i class="ri-edit-line"></i>
</a> </a>
<div class="data-tip">Bearbeiten</div> <div class="data-tip">Bearbeiten</div>
</tool-tip> </tool-tip>
<tool-tip position="top" class="inline">
<a href="/almanach/{{ $entry.MusenalmID }}/contents/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
<i class="ri-file-list-3-line"></i>
<span>{{ if .contents_count }}{{ .contents_count }}{{ else }}0{{ end }}</span>
</a>
<div class="data-tip">Beiträge bearbeiten</div>
</tool-tip>
<tool-tip position="top" class="inline"> <tool-tip position="top" class="inline">
<button <button
type="button" type="button"