More work on the new people search
This commit is contained in:
parent
fac4e695fc
commit
e6eec0dfaf
6 changed files with 337 additions and 121 deletions
|
|
@ -297,3 +297,25 @@ func FieldMoveDown(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return c.Redirect("/admin/fields")
|
return c.Redirect("/admin/fields")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FieldJSON(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
|
||||||
|
db, err := helpers.GetDatabase()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var field models.Field
|
||||||
|
result := db.Preload("List").Preload("List.ListItems").Find(&field, "id = ?", id)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(field)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -8,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.readonly.ch/bouzoure/pop-camarades/helpers"
|
"git.readonly.ch/bouzoure/pop-camarades/helpers"
|
||||||
|
"git.readonly.ch/bouzoure/pop-camarades/helpers/database"
|
||||||
"git.readonly.ch/bouzoure/pop-camarades/models"
|
"git.readonly.ch/bouzoure/pop-camarades/models"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
@ -59,11 +61,7 @@ func Members(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
filterSection := c.Query("se")
|
/*sqlFilterSections := allowedSections
|
||||||
filterArchive := c.Query("a")
|
|
||||||
filterSearch := c.Query("s")
|
|
||||||
|
|
||||||
sqlFilterSections := allowedSections
|
|
||||||
sqlFilterAppend := "IS NULL"
|
sqlFilterAppend := "IS NULL"
|
||||||
if filterArchive == "1" {
|
if filterArchive == "1" {
|
||||||
sqlFilterAppend = "IS NOT NULL"
|
sqlFilterAppend = "IS NOT NULL"
|
||||||
|
|
@ -116,9 +114,6 @@ func Members(c *fiber.Ctx) error {
|
||||||
).Count(&count)
|
).Count(&count)
|
||||||
}
|
}
|
||||||
|
|
||||||
page, _ := strconv.Atoi(c.Query("p"))
|
|
||||||
pagination := helpers.Paginate(50, int(count), page)
|
|
||||||
|
|
||||||
var people []models.Person
|
var people []models.Person
|
||||||
if len(filterSearch) > 0 {
|
if len(filterSearch) > 0 {
|
||||||
result := db.Unscoped().Offset(
|
result := db.Unscoped().Offset(
|
||||||
|
|
@ -162,28 +157,50 @@ func Members(c *fiber.Ctx) error {
|
||||||
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
var sections []models.Section
|
/*var sections []models.Section
|
||||||
db.Order(
|
db.Order(
|
||||||
"name collate nocase asc",
|
"name collate nocase asc",
|
||||||
).Find(
|
).Find(
|
||||||
§ions,
|
§ions,
|
||||||
"contains_members = ? AND id IN ?",
|
"contains_members = ? AND id IN ?",
|
||||||
true, sqlFilterSections,
|
true, sqlFilterSections,
|
||||||
)
|
)*/
|
||||||
|
|
||||||
|
searchJSON := c.Query("s")
|
||||||
|
var params database.PeopleSearchParams
|
||||||
|
|
||||||
|
if len(searchJSON) > 0 {
|
||||||
|
err = json.Unmarshal([]byte(searchJSON), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(c.Query("p"))
|
||||||
|
|
||||||
|
results, err := database.PeopleSearch(params, "members", 50, page)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sections []models.Section
|
||||||
|
db.Order("name collate nocase asc").Find(§ions, "contains_members = ?", true)
|
||||||
|
|
||||||
|
var fields []models.Field
|
||||||
|
db.Order("position asc").Find(&fields, "person_type = ?", "member")
|
||||||
|
|
||||||
return c.Render("people", fiber.Map{
|
return c.Render("people", fiber.Map{
|
||||||
"PageTitle": "Membres",
|
"PageTitle": "Membres",
|
||||||
"MembersPage": true,
|
"MembersPage": true,
|
||||||
"People": people,
|
"People": results.Results,
|
||||||
"Pagination": pagination,
|
"Pagination": results.Pagination,
|
||||||
"PermShow": permShow,
|
"PermShow": permShow,
|
||||||
"PermShowArchived": permShowArchived,
|
"PermShowArchived": permShowArchived,
|
||||||
|
"SearchJSON": searchJSON,
|
||||||
"Sections": sections,
|
"Sections": sections,
|
||||||
"FilterArchive": filterArchive,
|
"Fields": fields,
|
||||||
"FilterSection": filterSectionUint,
|
|
||||||
"FilterSearch": filterSearch,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
75
helpers/database/people.go
Normal file
75
helpers/database/people.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.readonly.ch/bouzoure/pop-camarades/helpers"
|
||||||
|
"git.readonly.ch/bouzoure/pop-camarades/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PeopleSearchParams struct {
|
||||||
|
Advanced bool `json:"advanced"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Section uint `json:"section"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Archived bool `json:"archived"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
PostalCode string `json:"postal_code"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Fields map[uint]any `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeopleSearchResults struct {
|
||||||
|
Results []models.Person
|
||||||
|
Count int64
|
||||||
|
Pagination helpers.Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
func PeopleSearch(params PeopleSearchParams, personType string, pageSize int, page int) (PeopleSearchResults, error) {
|
||||||
|
var results PeopleSearchResults
|
||||||
|
|
||||||
|
db, err := helpers.GetDatabase()
|
||||||
|
if err != nil {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sqlQuery string
|
||||||
|
var sqlParams []any
|
||||||
|
sqlQuery = `--sql
|
||||||
|
SELECT people.ID,
|
||||||
|
people.is_member,
|
||||||
|
people.is_contact,
|
||||||
|
people.first_name,
|
||||||
|
people.last_name,
|
||||||
|
people.address1,
|
||||||
|
people.postal_code,
|
||||||
|
people.city,
|
||||||
|
people.section_id,
|
||||||
|
sections.name AS Section__name
|
||||||
|
FROM people
|
||||||
|
INNER JOIN sections
|
||||||
|
ON people.section_id = sections.id
|
||||||
|
WHERE is_member = @is_member
|
||||||
|
AND is_contact = @is_contact`
|
||||||
|
|
||||||
|
if personType == "members" {
|
||||||
|
sqlParams = append(sqlParams, sql.Named("is_member", true))
|
||||||
|
sqlParams = append(sqlParams, sql.Named("is_contact", false))
|
||||||
|
} else if personType == "contacts" {
|
||||||
|
sqlParams = append(sqlParams, sql.Named("is_member", false))
|
||||||
|
sqlParams = append(sqlParams, sql.Named("is_contact", true))
|
||||||
|
} else {
|
||||||
|
return results, fmt.Errorf("unkown person type")
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlResult := db.Raw(sqlQuery, sqlParams...).Scan(&results.Results)
|
||||||
|
|
||||||
|
if sqlResult.Error != nil {
|
||||||
|
return results, sqlResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
3
main.go
3
main.go
|
|
@ -167,6 +167,9 @@ func main() {
|
||||||
app.Post("/contacts/:id<int;min(0)>/restore", controllers.ContactRestore)
|
app.Post("/contacts/:id<int;min(0)>/restore", controllers.ContactRestore)
|
||||||
app.Post("/contacts/:id<int;min(0)>/purge", controllers.ContactPurge)
|
app.Post("/contacts/:id<int;min(0)>/purge", controllers.ContactPurge)
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
app.Get("/fields/:id<int;min(0)>", controllers.FieldJSON)
|
||||||
|
|
||||||
// Account manage
|
// Account manage
|
||||||
app.Get("/account/manage", controllers.AccountManage)
|
app.Get("/account/manage", controllers.AccountManage)
|
||||||
app.Post("/account/manage", controllers.AccountManage)
|
app.Post("/account/manage", controllers.AccountManage)
|
||||||
|
|
|
||||||
105
static/search.js
105
static/search.js
|
|
@ -5,13 +5,13 @@ $(document).ready(function() {
|
||||||
|
|
||||||
$("#advanced").on("click", function() {
|
$("#advanced").on("click", function() {
|
||||||
if ($(this).data("state") === "true") {
|
if ($(this).data("state") === "true") {
|
||||||
$(this).find("i").removeClass("bi-chevron-double-up");
|
$(this).find("i").removeClass("bi-arrow-up");
|
||||||
$(this).find("i").addClass("bi-chevron-double-down");
|
$(this).find("i").addClass("bi-arrow-down");
|
||||||
$("#advanced-section").addClass("d-none");
|
$("#advanced-section").addClass("d-none");
|
||||||
$(this).data("state", "false");
|
$(this).data("state", "false");
|
||||||
} else {
|
} else {
|
||||||
$(this).find("i").removeClass("bi-chevron-double-down");
|
$(this).find("i").removeClass("bi-arrow-down");
|
||||||
$(this).find("i").addClass("bi-chevron-double-up");
|
$(this).find("i").addClass("bi-arrow-up");
|
||||||
$("#advanced-section").removeClass("d-none");
|
$("#advanced-section").removeClass("d-none");
|
||||||
$(this).data("state", "true");
|
$(this).data("state", "true");
|
||||||
}
|
}
|
||||||
|
|
@ -22,10 +22,7 @@ $(document).ready(function() {
|
||||||
var searchData = JSON.parse(json);
|
var searchData = JSON.parse(json);
|
||||||
for (const [key, value] of Object.entries(searchData)) {
|
for (const [key, value] of Object.entries(searchData)) {
|
||||||
if (key === "advanced") {
|
if (key === "advanced") {
|
||||||
if (value) {
|
if (value) $("#advanced").trigger("click");
|
||||||
$("#advanced").trigger("click");
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,16 +31,90 @@ $(document).ready(function() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "boolean" && value) {
|
if (typeof value === "boolean") {
|
||||||
console.log(key, value, typeof(value));
|
$("[data-search-field=" + key + "]").prop("checked", value);
|
||||||
$("[data-search-field=" + key + "]").prop("checked", true);
|
|
||||||
} else {
|
} else {
|
||||||
$("[data-search-field=" + key + "]").val(value);
|
$("[data-search-field=" + key + "]").val(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createFieldSelection();
|
||||||
|
$("body").on("change", ".search-fields-count .field-select", function() {
|
||||||
|
var lastfield = $(this).attr("data-last-field");
|
||||||
|
var field = $(this).val();
|
||||||
|
if (field == "") {
|
||||||
|
$(this).parents(".row").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastfield.length === 0) {
|
||||||
|
createFieldSelection();
|
||||||
|
}
|
||||||
|
$(this).attr("data-last-field", field);
|
||||||
|
|
||||||
|
$(this).parents(".row").find(".end-col").find("input").remove();
|
||||||
|
$(this).parents(".row").find(".end-col").find("select").remove();
|
||||||
|
|
||||||
|
$(elem).parents(".row").find(".end-col").append($("<input>", {
|
||||||
|
class: "form-control",
|
||||||
|
type: "text",
|
||||||
|
disabled: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
var elem = $(this);
|
||||||
|
$.getJSON("/fields/" + field, function(data) {
|
||||||
|
$(this).parents(".row").find(".end-col").find("input").remove();
|
||||||
|
|
||||||
|
if (data.FieldType === "list") {
|
||||||
|
var select = $("<select>", {
|
||||||
|
class: "form-select",
|
||||||
|
"data-optional-field": field
|
||||||
|
});
|
||||||
|
$(elem).parents(".row").find(".end-col").append(select);
|
||||||
|
|
||||||
|
for (const [_, value] of Object.entries(data.List.ListItems)) {
|
||||||
|
$(select).append($("<option>", {
|
||||||
|
value: value.ID,
|
||||||
|
text: value.Value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else if (data.FieldType === "number") {
|
||||||
|
$(elem).parents(".row").find(".end-col").append($("<input>", {
|
||||||
|
class: "form-control",
|
||||||
|
type: "number",
|
||||||
|
"data-optional-field": field
|
||||||
|
}));
|
||||||
|
} else if (data.FieldType === "date") {
|
||||||
|
$(elem).parents(".row").find(".end-col").append($("<input>", {
|
||||||
|
class: "form-control",
|
||||||
|
type: "date",
|
||||||
|
"data-optional-field": field
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
$(elem).parents(".row").find(".end-col").append($("<input>", {
|
||||||
|
class: "form-control",
|
||||||
|
type: "text",
|
||||||
|
"data-optional-field": field
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createFieldSelection() {
|
||||||
|
const uuid = crypto.randomUUID();
|
||||||
|
|
||||||
|
var elem = $("#search-fields-model").clone().appendTo("#search-fields");
|
||||||
|
$(elem).attr("id", "search-field-" + uuid);
|
||||||
|
//$(elem).find(".start-col").find("label").attr("for", "search-field-field-" + uuid);
|
||||||
|
//$(elem).find(".start-col").find("select").attr("id", "search-field-field-" + uuid);
|
||||||
|
//$(elem).find(".end-col").find("label").attr("for", "search-field-value-" + uuid);
|
||||||
|
//$(elem).find(".end-col").find("input").attr("id", "search-field-value-" + uuid);
|
||||||
|
$(elem).removeClass("d-none");
|
||||||
|
$(elem).addClass("search-fields-count");
|
||||||
|
}
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
var advancedSearch = ($("#advanced").data("state") === "true");
|
var advancedSearch = ($("#advanced").data("state") === "true");
|
||||||
var searchData = {
|
var searchData = {
|
||||||
|
|
@ -66,7 +137,17 @@ function search() {
|
||||||
searchData[index] = value;
|
searchData[index] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fields = {}
|
||||||
|
$("[data-optional-field]:not(:disabled)").each(function() {
|
||||||
|
var index = $(this).attr("data-optional-field");
|
||||||
|
var value = $(this).val();
|
||||||
|
|
||||||
|
fields[index] = value;
|
||||||
|
});
|
||||||
|
searchData["fields"] = fields;
|
||||||
|
|
||||||
var json = JSON.stringify(searchData);
|
var json = JSON.stringify(searchData);
|
||||||
$("#search-json").val(json);
|
$("#search-json").val(json);
|
||||||
$("#search-form").submit();
|
console.log(searchData);
|
||||||
|
//$("#search-form").submit();
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<i class="bi-plus-lg"></i>
|
<i class="bi-plus-lg"></i>
|
||||||
Ajouter
|
Ajouter
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-outline-primary" href="/members/export?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}">
|
<a class="btn btn-outline-primary" href="/members/export?s={{ SearchJSON|urlencode }}">
|
||||||
<i class="bi-filetype-csv"></i>
|
<i class="bi-filetype-csv"></i>
|
||||||
Exporter
|
Exporter
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
<i class="bi-plus-lg"></i>
|
<i class="bi-plus-lg"></i>
|
||||||
Ajouter
|
Ajouter
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-outline-primary" href="/contacts/export?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}">
|
<a class="btn btn-outline-primary" href="/contacts/export?s={{ SearchJSON|urlencode }}">
|
||||||
<i class="bi-filetype-csv"></i>
|
<i class="bi-filetype-csv"></i>
|
||||||
Exporter
|
Exporter
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -44,106 +44,124 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 row">
|
<div class="h-100 p-4 mb-3 bg-body-tertiary border rounded-3">
|
||||||
<div class="col-lg-6 mb-3">
|
|
||||||
|
|
||||||
<label for="name" class="form-label">
|
|
||||||
Nom
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="name" data-search-field="name" data-search-advanced="false" name="name">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-lg-3 mb-3">
|
|
||||||
|
|
||||||
<label for="section" class="form-label">
|
|
||||||
Section
|
|
||||||
</label>
|
|
||||||
<select class="form-select" id="section" data-search-field="section" data-search-advanced="false" name="section">
|
|
||||||
<option value="">Choisir...</option>
|
|
||||||
{% for Section in Sections %}
|
|
||||||
<option value="{{ Section.ID }}">
|
|
||||||
{{ Section.Name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-lg-3 mb-2">
|
|
||||||
|
|
||||||
<label class="form-label">
|
|
||||||
Status
|
|
||||||
</label>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" data-search-field="archive" data-search-advanced="false" id="archive">
|
|
||||||
<label class="form-check-label" for="archive">Archivé</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3 d-none" id="advanced-section">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 mb-3">
|
<div class="col-lg-6 mb-3">
|
||||||
|
|
||||||
<label for="email" class="form-label">
|
<label for="name" class="form-label">Nom et prénom</label>
|
||||||
Email
|
<input type="text" class="form-control" id="name" data-search-field="name" data-search-advanced="false" name="name">
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="email" data-search-field="email" data-search-advanced="true" name="email">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 mb-3">
|
<div class="col-sm-6 col-lg-3 mb-3">
|
||||||
|
|
||||||
<label for="phone" class="form-label">
|
<label for="section" class="form-label">Section</label>
|
||||||
Téléphone
|
<select class="form-select" id="section" data-search-field="section" data-search-advanced="false" name="section">
|
||||||
</label>
|
<option value=""></option>
|
||||||
<input type="text" class="form-control" id="phone" data-search-field="phone" data-search-advanced="true" name="phone">
|
{% for Section in Sections %}
|
||||||
|
<option value="{{ Section.ID }}">
|
||||||
|
{{ Section.Name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 mb-3">
|
<div class="col-sm-6 col-lg-3 mb-3 pt-3">
|
||||||
|
|
||||||
<label for="address" class="form-label">
|
<div class="form-check form-switch">
|
||||||
Adresse
|
<input class="form-check-input" type="checkbox" role="switch" data-search-field="active" data-search-advanced="false" id="active" checked>
|
||||||
</label>
|
{% if MembersPage %}
|
||||||
<input type="text" class="form-control" id="address" data-search-field="address" data-search-advanced="true" name="address">
|
<label class="form-check-label" for="active">Afficher membres actifs</label>
|
||||||
|
{% else %}
|
||||||
|
<label class="form-check-label" for="active">Afficher contactss actifs</label>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
<div class="form-check form-switch">
|
||||||
<div class="col-sm-4 col-lg-2 mb-3">
|
<input class="form-check-input" type="checkbox" role="switch" data-search-field="archive" data-search-advanced="false" id="archive">
|
||||||
|
{% if MembersPage %}
|
||||||
<label for="postal_code" class="form-label">
|
<label class="form-check-label" for="archive">Afficher membres archivés</label>
|
||||||
Code postal
|
{% else %}
|
||||||
</label>
|
<label class="form-check-label" for="archive">Afficher contacts archivés</label>
|
||||||
<input type="text" class="form-control" id="postal_code" data-search-field="postal_code" data-search-advanced="true" name="postal_code">
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-sm-8 col-lg-4 mb-3">
|
|
||||||
|
|
||||||
<label for="city" class="form-label">
|
|
||||||
Lieu
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="city" data-search-field="city" data-search-advanced="true" name="city">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none" id="advanced-section">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 mb-3">
|
||||||
|
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="text" class="form-control" id="email" data-search-field="email" data-search-advanced="true" name="email">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 mb-3">
|
||||||
|
|
||||||
|
<label for="phone" class="form-label">Téléphone</label>
|
||||||
|
<input type="text" class="form-control" id="phone" data-search-field="phone" data-search-advanced="true" name="phone">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 mb-3">
|
||||||
|
|
||||||
|
<label for="address" class="form-label">Adresse</label>
|
||||||
|
<input type="text" class="form-control" id="address" data-search-field="address" data-search-advanced="true" name="address">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-lg-2 mb-3">
|
||||||
|
|
||||||
|
<label for="postal_code" class="form-label">Code postal</label>
|
||||||
|
<input type="text" class="form-control" id="postal_code" data-search-field="postal_code" data-search-advanced="true" name="postal_code">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 col-lg-4 mb-3">
|
||||||
|
|
||||||
|
<label for="city" class="form-label">Lieu</label>
|
||||||
|
<input type="text" class="form-control" id="city" data-search-field="city" data-search-advanced="true" name="city">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="search-fields-model" class="row d-none">
|
||||||
|
<div class="start-col col-lg-6 mb-3">
|
||||||
|
<label class="form-label">Champ supplémentaire</label>
|
||||||
|
<select class="form-select field-select" data-last-field="">
|
||||||
|
<option value=""></option>
|
||||||
|
{% for Field in Fields %}
|
||||||
|
<option value="{{ Field.ID }}">
|
||||||
|
{{ Field.Name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="end-col col-lg-6 mb-3">
|
||||||
|
<label class="form-label">Valeur</label>
|
||||||
|
<input type="text" class="form-control" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="search-fields" class="mb-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-6">
|
||||||
|
<button class="btn btn-outline-primary" id="advanced" data-state="false" type="button">
|
||||||
|
<i class="bi-arrow-down me-1"></i> Avancé
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 text-end">
|
||||||
|
<button class="btn btn-outline-success" id="search" type="button">
|
||||||
|
<i class="bi-search me-1"></i> Recherche
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="search-form" method="get" class="d-none">
|
||||||
|
<input type="hidden" name="p" value="{{ Pagination.CurrentPage }}">
|
||||||
|
<input type="hidden" id="search-json" name="s" value="{{ SearchJSON }}">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row my-3">
|
|
||||||
<div class="col-6">
|
|
||||||
<button class="btn btn-outline-primary btn-sm" id="advanced" data-state="false" type="button">
|
|
||||||
<i class="bi-chevron-double-down me-1"></i> Avancé
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 text-end">
|
|
||||||
<button class="btn btn-outline-success btn-sm" id="search" type="button">
|
|
||||||
<i class="bi-search me-1"></i> Recherche
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="search-form" method="get" class="my-3 d-none">
|
|
||||||
<input type="hidden" name="p" value="{{ Pagination.CurrentPage }}">
|
|
||||||
<input type="hidden" id="search-json" name="s" value="{{ FilterSearch }}">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -203,7 +221,7 @@
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link"
|
class="page-link"
|
||||||
href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p=1"
|
href="?p=1&s={{ SearchJSON|urlencode }}"
|
||||||
>
|
>
|
||||||
<i class="bi-rewind"></i>
|
<i class="bi-rewind"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -211,7 +229,7 @@
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link"
|
class="page-link"
|
||||||
href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ Pagination.CurrentPage - 1 }}"
|
href="?&p={{ Pagination.CurrentPage - 1 }}&s={{ SearchJSON|urlencode }}"
|
||||||
>
|
>
|
||||||
<i class="bi-caret-left"></i>
|
<i class="bi-caret-left"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -227,7 +245,7 @@
|
||||||
class="page-link"
|
class="page-link"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ i }}"
|
href="?&p={{ i }}&s={{ SearchJSON|urlencode }}"
|
||||||
>
|
>
|
||||||
{{ i }}
|
{{ i }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -249,7 +267,7 @@
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link"
|
class="page-link"
|
||||||
href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ Pagination.CurrentPage + 1 }}"
|
href="?&p={{ Pagination.CurrentPage + 1 }}&s={{ SearchJSON|urlencode }}"
|
||||||
>
|
>
|
||||||
<i class="bi-caret-right"></i>
|
<i class="bi-caret-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -257,7 +275,7 @@
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link"
|
class="page-link"
|
||||||
href="?se={{ FilterSection }}&s={{ FilterSearch|urlencode }}&a={{ FilterArchive }}&p={{ Pagination.MaxPages }}"
|
href="?p={{ Pagination.MaxPages }}&s={{ SearchJSON|urlencode }}"
|
||||||
>
|
>
|
||||||
<i class="bi-fast-forward"></i>
|
<i class="bi-fast-forward"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue