mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
feat: Add partial search results rendering for file metadata
- Implement HandleFileMetadataSearchPartial handler for HTMX-powered search results - Update file_metadata.templ to use new /files/search/partial endpoint - Add HTMX request detection and browser redirect for search results - Enhance search results pagination and filtering with partial rendering - Improve user experience with dynamic, asynchronous search interactions
This commit is contained in:
@@ -1000,7 +1000,7 @@ templ FileMetadataSearch(ctx context.Context, data FileMetadataSearchData) {
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form
|
||||
hx-get="/files/search"
|
||||
hx-get="/files/search/partial"
|
||||
hx-target="#search-results-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#search-form-loading"
|
||||
@@ -1158,7 +1158,7 @@ templ FileMetadataSearchContent(data FileMetadataSearchData) {
|
||||
<div class="flex justify-center items-center py-4 bg-secondary-50 dark:bg-secondary-800 rounded-lg">
|
||||
<div class="flex gap-2">
|
||||
if data.Page > 1 {
|
||||
<a hx-get={ fmt.Sprintf("/files/search?page=%d&limit=%d&status=%s&filename=%s&job_id=%s&hash=%s&start_date=%s&end_date=%s", data.Page - 1, data.Limit, data.Filter.Status, data.Filter.FileName, data.Filter.JobID, data.Filter.Hash, data.Filter.StartDate, data.Filter.EndDate) }
|
||||
<a hx-get={ fmt.Sprintf("/files/search/partial?page=%d&limit=%d&status=%s&filename=%s&job_id=%s&hash=%s&start_date=%s&end_date=%s", data.Page - 1, data.Limit, data.Filter.Status, data.Filter.FileName, data.Filter.JobID, data.Filter.Hash, data.Filter.StartDate, data.Filter.EndDate) }
|
||||
hx-target="#search-results-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-headers='{"X-HX-Request": "true"}'
|
||||
@@ -1177,7 +1177,7 @@ templ FileMetadataSearchContent(data FileMetadataSearchData) {
|
||||
{ strconv.Itoa(i) }
|
||||
</span>
|
||||
} else if i == 1 || i == data.TotalPages || (i >= data.Page-2 && i <= data.Page+2) {
|
||||
<a hx-get={ fmt.Sprintf("/files/search?page=%d&limit=%d&status=%s&filename=%s&job_id=%s&hash=%s&start_date=%s&end_date=%s", i, data.Limit, data.Filter.Status, data.Filter.FileName, data.Filter.JobID, data.Filter.Hash, data.Filter.StartDate, data.Filter.EndDate) }
|
||||
<a hx-get={ fmt.Sprintf("/files/search/partial?page=%d&limit=%d&status=%s&filename=%s&job_id=%s&hash=%s&start_date=%s&end_date=%s", i, data.Limit, data.Filter.Status, data.Filter.FileName, data.Filter.JobID, data.Filter.Hash, data.Filter.StartDate, data.Filter.EndDate) }
|
||||
hx-target="#search-results-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-headers='{"X-HX-Request": "true"}'
|
||||
@@ -1190,7 +1190,7 @@ templ FileMetadataSearchContent(data FileMetadataSearchData) {
|
||||
}
|
||||
|
||||
if data.Page < data.TotalPages {
|
||||
<a hx-get={ fmt.Sprintf("/files/search?page=%d&limit=%d&status=%s&filename=%s&job_id=%s&hash=%s&start_date=%s&end_date=%s", data.Page + 1, data.Limit, data.Filter.Status, data.Filter.FileName, data.Filter.JobID, data.Filter.Hash, data.Filter.StartDate, data.Filter.EndDate) }
|
||||
<a hx-get={ fmt.Sprintf("/files/search/partial?page=%d&limit=%d&status=%s&filename=%s&job_id=%s&hash=%s&start_date=%s&end_date=%s", data.Page + 1, data.Limit, data.Filter.Status, data.Filter.FileName, data.Filter.JobID, data.Filter.Hash, data.Filter.StartDate, data.Filter.EndDate) }
|
||||
hx-target="#search-results-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-headers='{"X-HX-Request": "true"}'
|
||||
|
||||
@@ -24,6 +24,7 @@ func (h *FileMetadataHandler) Register(router *gin.RouterGroup) {
|
||||
fileGroup.GET("/:id", h.GetFileMetadataDetails)
|
||||
fileGroup.GET("/job/:job_id", h.GetFileMetadataForJob)
|
||||
fileGroup.GET("/search", h.SearchFileMetadata)
|
||||
fileGroup.GET("/search/partial", h.HandleFileMetadataSearchPartial)
|
||||
fileGroup.DELETE("/:id", h.DeleteFileMetadata)
|
||||
fileGroup.GET("/partial", h.HandleFileMetadataPartial)
|
||||
}
|
||||
@@ -425,11 +426,24 @@ func (h *FileMetadataHandler) DeleteFileMetadata(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// HandleFileMetadataPartial handles partial updates for file metadata list pagination
|
||||
// HandleFileMetadataPartial handles rendering just the partial template for file metadata
|
||||
func (h *FileMetadataHandler) HandleFileMetadataPartial(c *gin.Context) {
|
||||
// Check if this is an HTMX request or a direct browser request
|
||||
isHtmxRequest := c.GetHeader("HX-Request") == "true"
|
||||
|
||||
// If it's a direct browser request (not from HTMX), redirect to the full page
|
||||
if !isHtmxRequest {
|
||||
// Get all query parameters
|
||||
query := c.Request.URL.Query()
|
||||
|
||||
// Rebuild query string for the redirect
|
||||
c.Redirect(http.StatusFound, "/files?"+query.Encode())
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetUint("userID")
|
||||
|
||||
// Query parameters for pagination and filtering
|
||||
// Query parameters for filtering and pagination
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@@ -517,3 +531,115 @@ func (h *FileMetadataHandler) HandleFileMetadataPartial(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/html")
|
||||
components.FileMetadataListPartial(data).Render(ctx, c.Writer)
|
||||
}
|
||||
|
||||
// HandleFileMetadataSearchPartial handles partial updates for search results
|
||||
func (h *FileMetadataHandler) HandleFileMetadataSearchPartial(c *gin.Context) {
|
||||
// Check if this is an HTMX request or a direct browser request
|
||||
isHtmxRequest := c.GetHeader("HX-Request") == "true"
|
||||
|
||||
// If it's a direct browser request (not from HTMX), redirect to the full page
|
||||
if !isHtmxRequest {
|
||||
// Get all query parameters
|
||||
query := c.Request.URL.Query()
|
||||
|
||||
// Rebuild query string for the redirect
|
||||
c.Redirect(http.StatusFound, "/files/search?"+query.Encode())
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetUint("userID")
|
||||
|
||||
// Query parameters for search and pagination
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
||||
if limit < 1 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
status := c.Query("status")
|
||||
jobIDStr := c.Query("job_id")
|
||||
fileName := c.Query("filename")
|
||||
hash := c.Query("hash")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
// Execute the search query
|
||||
query := h.DB.DB.Model(&db.FileMetadata{}).Joins("JOIN jobs ON file_metadata.job_id = jobs.id")
|
||||
|
||||
// Apply filters
|
||||
if jobIDStr != "" {
|
||||
jobID, _ := strconv.ParseUint(jobIDStr, 10, 64)
|
||||
query = query.Where("file_metadata.job_id = ?", jobID)
|
||||
} else {
|
||||
// Only show files from jobs created by the current user
|
||||
query = query.Where("jobs.created_by = ?", userID)
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("file_metadata.status = ?", status)
|
||||
}
|
||||
|
||||
if fileName != "" {
|
||||
query = query.Where("file_metadata.file_name LIKE ?", "%"+fileName+"%")
|
||||
}
|
||||
|
||||
if hash != "" {
|
||||
query = query.Where("file_metadata.file_hash = ?", hash)
|
||||
}
|
||||
|
||||
if startDate != "" {
|
||||
query = query.Where("file_metadata.processed_time >= ?", startDate)
|
||||
}
|
||||
|
||||
if endDate != "" {
|
||||
query = query.Where("file_metadata.processed_time <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
// Count total results
|
||||
var totalCount int64
|
||||
if err := query.Count(&totalCount).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count files"})
|
||||
return
|
||||
}
|
||||
|
||||
// Order and paginate the results
|
||||
var files []db.FileMetadata
|
||||
if err := query.
|
||||
Preload("Job").
|
||||
Order("file_metadata.processed_time DESC").
|
||||
Limit(limit).
|
||||
Offset((page - 1) * limit).
|
||||
Find(&files).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to search files"})
|
||||
return
|
||||
}
|
||||
|
||||
// Build the template data
|
||||
data := components.FileMetadataSearchData{
|
||||
Files: files,
|
||||
TotalCount: totalCount,
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
TotalPages: int(totalCount) / limit,
|
||||
Filter: components.FileMetadataFilter{
|
||||
Status: status,
|
||||
JobID: jobIDStr,
|
||||
FileName: fileName,
|
||||
Hash: hash,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
},
|
||||
}
|
||||
|
||||
if int(totalCount)%limit > 0 {
|
||||
data.TotalPages++
|
||||
}
|
||||
|
||||
ctx := context.WithValue(c.Request.Context(), "userID", userID)
|
||||
c.Header("Content-Type", "text/html")
|
||||
components.FileMetadataSearchContent(data).Render(ctx, c.Writer)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user