diff --git a/controllers/contacts.go b/controllers/contacts.go index bdc1866..4fb8090 100644 --- a/controllers/contacts.go +++ b/controllers/contacts.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "time" "git.readonly.ch/bouzoure/pop-camarades/helpers" @@ -45,26 +46,374 @@ func Contacts(c *fiber.Ctx) error { return err } - var people []models.Person - result := db.Order( - "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( - &people, "is_contact = ? AND section_id IN ?", true, allowedSections, - ) + filterSection := c.Query("se") + filterArchive := c.Query("a") + filterSearch := c.Query("s") - if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { - return err + sqlFilterSections := allowedSections + sqlFilterAppend := "IS NULL" + if filterArchive == "1" { + sqlFilterAppend = "IS NOT NULL" + sqlFilterSections = allowedSectionsArchived } + var filterSectionUint uint + if len(filterSection) > 0 { + filterSectionUint64, err := strconv.ParseUint(filterSection, 10, 0) + if err == nil { + for _, s := range sqlFilterSections { + if s == uint(filterSectionUint64) { + filterSectionUint = uint(filterSectionUint64) + } + } + } + } + + if filterSectionUint > 0 { + sqlFilterAppend = fmt.Sprintf( + "%s AND section_id = %d", + sqlFilterAppend, + filterSectionUint, + ) + } + + var sqlFilterSearch string + if len(filterSearch) > 0 { + sqlFilterSearch = fmt.Sprintf( + "%%%s%%", filterSearch, + ) + } + + var count int64 + if len(filterSearch) > 0 { + db.Model(&models.Person{}).Where( + fmt.Sprintf( + "is_contact = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", + sqlFilterAppend, + ), + true, allowedSections, sqlFilterSearch, sqlFilterSearch, + ).Count(&count) + } else { + db.Model(&models.Person{}).Where( + fmt.Sprintf( + "is_contact = ? AND section_id IN ? AND deleted_at %s", + sqlFilterAppend, + ), + true, allowedSections, + ).Count(&count) + } + + page, _ := strconv.Atoi(c.Query("p")) + pagination := helpers.Paginate(50, int(count), page) + + var people []models.Person + if len(filterSearch) > 0 { + result := db.Unscoped().Offset( + pagination.Offset, + ).Limit( + pagination.PageSize, + ).Order( + "last_name collate nocase asc, first_name collate nocase asc", + ).Preload( + "Section", + ).Find( + &people, + fmt.Sprintf( + "is_contact = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", + sqlFilterAppend, + ), + true, sqlFilterSections, sqlFilterSearch, sqlFilterSearch, + ) + + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return err + } + } else { + result := db.Unscoped().Offset( + pagination.Offset, + ).Limit( + pagination.PageSize, + ).Order( + "last_name collate nocase asc, first_name collate nocase asc", + ).Preload( + "Section", + ).Find( + &people, + fmt.Sprintf( + "is_contact = ? AND section_id IN ? AND deleted_at %s", + sqlFilterAppend, + ), + true, sqlFilterSections, + ) + + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return err + } + } + + var sections []models.Section + db.Order( + "name collate nocase asc", + ).Find( + §ions, sqlFilterSections, + ) + return c.Render("people", fiber.Map{ "PageTitle": "Contacts", "MembersPage": false, "People": people, + "Pagination": pagination, "PermShow": permShow, "PermShowArchived": permShowArchived, + "Sections": sections, + "FilterArchive": filterArchive, + "FilterSection": filterSectionUint, + "FilterSearch": filterSearch, }) } +func ContactsExport(c *fiber.Ctx) error { + userid, err := helpers.GetSessionUserId(c) + if err != nil { + return err + } + + allowedSections, err := helpers.PermissionsGetSections( + userid, "show_contact", + ) + if err != nil { + return err + } + + allowedSectionsArchived, err := helpers.PermissionsGetSections( + userid, "show_archived_contact", + ) + if err != nil { + return err + } + + permShow := (len(allowedSections) > 0) + permShowArchived := (len(allowedSectionsArchived) > 0) + + if !permShow && !permShowArchived { + return fiber.NewError(fiber.StatusForbidden, "Forbidden") + } + + db, err := helpers.GetDatabase() + if err != nil { + return err + } + + filterSection := c.Query("se") + filterArchive := c.Query("a") + filterSearch := c.Query("s") + + sqlFilterSections := allowedSections + sqlFilterAppend := "IS NULL" + if filterArchive == "1" { + sqlFilterAppend = "IS NOT NULL" + sqlFilterSections = allowedSectionsArchived + } + + var filterSectionUint uint + if len(filterSection) > 0 { + filterSectionUint64, err := strconv.ParseUint(filterSection, 10, 0) + if err == nil { + for _, s := range sqlFilterSections { + if s == uint(filterSectionUint64) { + filterSectionUint = uint(filterSectionUint64) + } + } + } + } + + if filterSectionUint > 0 { + sqlFilterAppend = fmt.Sprintf( + "%s AND section_id = %d", + sqlFilterAppend, + filterSectionUint, + ) + } + + var sqlFilterSearch string + if len(filterSearch) > 0 { + sqlFilterSearch = fmt.Sprintf( + "%%%s%%", filterSearch, + ) + } + + var people []models.Person + if len(filterSearch) > 0 { + result := db.Unscoped().Order( + "last_name collate nocase asc, first_name collate nocase asc", + ).Preload("Section").Find( + &people, + fmt.Sprintf( + "is_contact = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", + sqlFilterAppend, + ), + true, sqlFilterSections, sqlFilterSearch, sqlFilterSearch, + ) + + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return err + } + } else { + result := db.Unscoped().Order( + "last_name collate nocase asc, first_name collate nocase asc", + ).Preload("Section").Find( + &people, + fmt.Sprintf( + "is_contact = ? AND section_id IN ? AND deleted_at %s", + sqlFilterAppend, + ), + true, sqlFilterSections, + ) + + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return err + } + } + + var fields []models.Field + db.Order("name collate nocase asc").Preload( + "List", + ).Find( + &fields, "person_type = ?", "contact", + ) + + var fieldValues []models.FieldValue + db.Preload("ListItem").Find(&fieldValues) + + c.Set("Content-Type", "text/csv") + c.Set("Content-Disposition", fmt.Sprintf( + "attachment;filename=contacts_%s.csv", + time.Now().Format(time.RFC3339), + )) + + csvFields := []string{ + "first_name", + "last_name", + "email", + "phone", + "mobile", + "address1", + "address2", + "postal_code", + "city", + "section_id", + "section_name", + } + + for _, field := range fields { + csvFields = append(csvFields, field.Name) + } + + for key, csvField := range csvFields { + csvField = strings.ReplaceAll(csvField, "\r", "") + csvField = strings.ReplaceAll(csvField, "\n", " ") + csvField = strings.ReplaceAll(csvField, ",", "_") + csvField = strings.ReplaceAll(csvField, ";", "_") + + if key+1 == len(csvFields) { + c.Writef("\"%s\"\n", csvField) + } else { + c.Writef("\"%s\";", csvField) + } + } + + for _, person := range people { + c.Writef("\"%s\";", person.FirstName) + c.Writef("\"%s\";", person.LastName) + c.Writef("\"%s\";", person.Email) + c.Writef("\"%s\";", person.Phone) + c.Writef("\"%s\";", person.Mobile) + c.Writef("\"%s\";", person.Address1) + c.Writef("\"%s\";", person.Address2) + c.Writef("\"%s\";", person.PostalCode) + c.Writef("\"%s\";", person.City) + c.Writef("\"%d\";", person.SectionID) + + if len(fields) > 0 { + c.Writef("\"%s\";", person.Section.Name) + } else { + c.Writef("\"%s\"\n", person.Section.Name) + } + + for key, field := range fields { + endLine := ";" + if key+1 == len(fields) { + endLine = "\n" + } + + countMulti := 0 + found := false + for _, value := range fieldValues { + if value.FieldID == field.ID && value.PersonID == person.ID { + found = true + + if field.FieldType == "text" || field.FieldType == "longtext" { + + text := value.ValueString.String + text = strings.ReplaceAll(text, "\r", "") + text = strings.ReplaceAll(text, "\n", " ") + text = strings.ReplaceAll(text, ",", "_") + text = strings.ReplaceAll(text, ";", "_") + + c.Writef("\"%s\"%s", text, endLine) + + } else if field.FieldType == "number" { + + if value.ValueInt.Valid { + c.Writef("\"%d\"%s", value.ValueInt.Int64, endLine) + } else { + c.Writef("\"\"%s", endLine) + } + + } else if field.FieldType == "date" { + + if value.ValueDate.Valid { + date := value.ValueDate.Time.Format("2006-01-02") + c.Writef("\"%s\"%s", date, endLine) + } else { + c.Writef("\"\"%s", endLine) + } + + } else if field.FieldType == "list" { + + if field.List.Multi { + + if countMulti == 0 { + c.Writef("\"") + } else { + c.Writef(",") + } + + c.Writef("%s", value.ListItem.Value) + countMulti++ + + } else { + c.Writef("\"%s\"%s", value.ListItem.Value, endLine) + } + + } + + } + } + + if countMulti > 0 { + c.Writef("\"%s", endLine) + } + + if !found { + c.Writef("\"\"%s", endLine) + } + } + } + + return nil +} + func ContactShow(c *fiber.Ctx) error { id := c.Params("id") @@ -78,14 +427,14 @@ func ContactShow(c *fiber.Ctx) error { &person, "id = ? AND is_contact", id, true, ) - if result.RowsAffected < 1 { - return fiber.NewError(fiber.StatusNotFound, "Not found") - } - if result.Error != nil { return result.Error } + if result.RowsAffected < 1 { + return fiber.NewError(fiber.StatusNotFound, "Not found") + } + userid, err := helpers.GetSessionUserId(c) if err != nil { return err diff --git a/controllers/members.go b/controllers/members.go index 93cd7f6..33b8d94 100644 --- a/controllers/members.go +++ b/controllers/members.go @@ -116,45 +116,20 @@ func Members(c *fiber.Ctx) error { ).Count(&count) } - pageQuery := c.Query("p") - page, _ := strconv.Atoi(pageQuery) - if page <= 0 { - page = 1 - } - - pageSize := 50 - maxPages := 1 - - if count > int64(pageSize) { - maxPages = int(count) / pageSize - } - - if page > maxPages { - page = 1 - } - - offset := (page - 1) * pageSize - - var pages []int - for i := 1; i <= maxPages; i++ { - if i == page { - pages = append(pages, i) - } else if i >= (page-2) && i < page { - pages = append(pages, i) - } else if i <= (page+2) && i > page { - pages = append(pages, i) - } else if page <= 2 && i <= 5 { - pages = append(pages, i) - } else if page >= (maxPages-2) && i >= (maxPages-5) { - pages = append(pages, i) - } - } + page, _ := strconv.Atoi(c.Query("p")) + pagination := helpers.Paginate(50, int(count), page) var people []models.Person if len(filterSearch) > 0 { - result := db.Unscoped().Offset(offset).Limit(pageSize).Order( + result := db.Unscoped().Offset( + pagination.Offset, + ).Limit( + pagination.PageSize, + ).Order( "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( + ).Preload( + "Section", + ).Find( &people, fmt.Sprintf( "is_member = ? AND section_id IN ? AND (first_name LIKE ? OR last_name LIKE ?) AND deleted_at %s", @@ -167,9 +142,15 @@ func Members(c *fiber.Ctx) error { return err } } else { - result := db.Unscoped().Offset(offset).Limit(pageSize).Order( + result := db.Unscoped().Offset( + pagination.Offset, + ).Limit( + pagination.PageSize, + ).Order( "last_name collate nocase asc, first_name collate nocase asc", - ).Preload("Section").Find( + ).Preload( + "Section", + ).Find( &people, fmt.Sprintf( "is_member = ? AND section_id IN ? AND deleted_at %s", @@ -194,9 +175,7 @@ func Members(c *fiber.Ctx) error { "PageTitle": "Membres", "MembersPage": true, "People": people, - "Page": page, - "Pages": pages, - "MaxPages": maxPages, + "Pagination": pagination, "PermShow": permShow, "PermShowArchived": permShowArchived, "Sections": sections, diff --git a/helpers/pagination.go b/helpers/pagination.go new file mode 100644 index 0000000..45bfab1 --- /dev/null +++ b/helpers/pagination.go @@ -0,0 +1,68 @@ +package helpers + +type Pagination struct { + PageSize int + MaxPages int + CurrentPage int + Offset int + Count int + Pages []int +} + +func Paginate(size int, count int, page int) Pagination { + if size < 1 { + size = 1 + } + + if page < 1 { + page = 1 + } + + pagination := Pagination{ + PageSize: size, + CurrentPage: page, + Count: count, + MaxPages: 1, + } + + if pagination.Count > pagination.PageSize { + pagination.MaxPages = pagination.Count / pagination.PageSize + } + + if pagination.CurrentPage > pagination.MaxPages { + pagination.CurrentPage = 1 + } + + pagination.Offset = (pagination.CurrentPage - 1) / pagination.PageSize + + for i := 1; i <= pagination.MaxPages; i++ { + + if i == pagination.CurrentPage { + pagination.Pages = append(pagination.Pages, i) + continue + } + + if i >= (pagination.CurrentPage-2) && i < pagination.CurrentPage { + pagination.Pages = append(pagination.Pages, i) + continue + } + + if i <= (pagination.CurrentPage+2) && i > pagination.CurrentPage { + pagination.Pages = append(pagination.Pages, i) + continue + } + + if pagination.CurrentPage <= 2 && i <= 5 { + pagination.Pages = append(pagination.Pages, i) + continue + } + + if pagination.CurrentPage >= (pagination.MaxPages-2) && i >= (pagination.MaxPages-5) { + pagination.Pages = append(pagination.Pages, i) + continue + } + + } + + return pagination +} diff --git a/main.go b/main.go index d1ce997..befca21 100644 --- a/main.go +++ b/main.go @@ -136,6 +136,7 @@ func main() { // Contacts app.Get("/contacts", controllers.Contacts) + app.Get("/contacts/export", controllers.ContactsExport) app.Get("/contacts/:id", controllers.ContactShow) app.Get("/contacts/add", controllers.ContactAdd) app.Post("/contacts/add", controllers.ContactAdd) diff --git a/views/people.html b/views/people.html index 1dfc381..8695a31 100644 --- a/views/people.html +++ b/views/people.html @@ -45,7 +45,7 @@
- +
@@ -181,7 +181,7 @@