github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/GUI/onglets/camps.go (about) 1 package onglets 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/benoitkugler/goACVE/client/GUI/onglets/notifications" 8 9 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 10 11 "github.com/benoitkugler/goACVE/client/GUI/fields" 12 13 "github.com/benoitkugler/goACVE/client/GUI/lists" 14 15 "github.com/therecipe/qt/core" 16 17 "github.com/benoitkugler/goACVE/client/GUI/basic" 18 "github.com/benoitkugler/goACVE/client/GUI/common" 19 "github.com/benoitkugler/goACVE/client/controllers" 20 "github.com/therecipe/qt/widgets" 21 22 dm "github.com/benoitkugler/goACVE/server/core/datamodel" 23 ) 24 25 func (c *Camps) Render() { 26 c.camps.table.Model().SetData(c.controller.ListeCamps) 27 if c.controller.Etat.CampCurrent != nil { 28 c.camps.table.SelectRow(c.controller.Etat.CampCurrent) 29 } 30 c.inscrits.Model().SetData(c.controller.ListeInscrits) 31 c.attente.Model().SetData(c.controller.ListeAttente) 32 if idParticipant := c.controller.Etat.IdParticipant; idParticipant != nil { 33 c.inscrits.SelectRow(idParticipant) // pas très subtil 34 c.attente.SelectRow(idParticipant) // mais pourquoi pas 35 } 36 c.stats.SetData(c.controller.GetStats()) 37 c.updateToInscrits() 38 39 // on affiche une fois au démarrage les notifications 40 if c.IsActif() && !c.hasShownNotifications { 41 inscrits, attentes := c.controller.CheckListeAttentes() 42 if len(attentes) > 0 { // les notifs inscrits sont moins importantes 43 c.hasShownNotifications = true 44 c.notifieAttentesExt(inscrits, attentes) 45 } 46 } 47 } 48 49 func (c *Camps) updateToInscrits() { 50 var enabledToInscrit bool 51 if idPart, ok := c.controller.Etat.IdParticipant.(rd.IdParticipant); ok { 52 isInscrit := c.controller.Base.NewParticipant(idPart.Int64()).RawData().ListeAttente.IsInscrit() 53 enabledToInscrit = !isInscrit 54 } 55 c.toInscrits.SetEnabled(enabledToInscrit) 56 } 57 58 func (c *Camps) ConfirmeNotifPlaceLiberee(info string) (keep bool, notifie bool) { 59 d := basic.Dialog("Passage en liste principale") 60 d.Layout().AddWidget(basic.Label(info)) 61 btnEnvoi := basic.Button("Envoyer un message") 62 btnEnvoi.ConnectClicked(func(_ bool) { 63 notifie = true 64 d.Accept() 65 }) 66 btnNoEnvoi := basic.Button("Ne pas notifier") 67 btnNoEnvoi.ConnectClicked(func(_ bool) { 68 notifie = false 69 d.Accept() 70 }) 71 72 btns := basic.Frame() 73 btnsL := widgets.NewQHBoxLayout2(btns) 74 btnsL.AddStretch(2) 75 btnsL.AddWidget(btnNoEnvoi, 1, 0) 76 btnsL.AddWidget(btnEnvoi, 1, 0) 77 d.Layout().AddWidget(btns) 78 keep = d.Exec() > 0 79 return keep, notifie 80 } 81 82 func (c *Camps) ConfirmeAjoutParticipant(camp dm.AccesCamp, personne rd.Personne, causes []string) bool { 83 msg := fmt.Sprintf("Confirmez-vous l'ajout de %s au %s ?", personne.NomPrenom(), camp.RawData().Label()) 84 if len(causes) > 0 { 85 for i, cause := range causes { 86 causes[i] = fmt.Sprintf("<b>%s</b>", cause) 87 } 88 msg += fmt.Sprintf(`<br/>Certaines conditions ne sont pas remplies : <br/> 89 <b>%s</b> <br/><br/>`, strings.Join(causes, "<br/>")) 90 } 91 return basic.Confirmation(msg, "Ajouter", basic.ONAdd) 92 } 93 94 func (c *Camps) ConfirmeSupprimeParticipant(msg string) bool { 95 return basic.Confirmation(msg, "Supprimer", basic.ONDelete) 96 } 97 98 type Camps struct { 99 *widgets.QFrame 100 controller *controllers.Camps 101 parent *widgets.QTabWidget 102 toolbar *widgets.QToolBar 103 104 camps listeCamps 105 inscrits, attente lists.Table 106 stats common.Statistics 107 toInscrits *widgets.QPushButton 108 109 hasShownNotifications bool 110 } 111 112 func newCamps(ct *controllers.Camps) *Camps { 113 p := Camps{QFrame: basic.Frame(), controller: ct} 114 p.camps = newListeCamps(ct) 115 p.inscrits = lists.Table{ 116 Liste: lists.Liste{ 117 Headers: ct.HeaderInscrits, 118 OnClick: func(acces rd.Item, _ int) { 119 p.onClick(acces, true) 120 }, 121 OnDoubleClick: func(acces rd.Item, _ int) { 122 p.onDoubleClick(acces) 123 }, 124 OnRightClick: func(acces rd.Item, pos *core.QPoint) { 125 p.onRightClick(acces, p.inscrits.MapToGlobal(pos)) 126 }, 127 OnSort: func(field rd.Field, reverse bool) { 128 lists.DefaultSort(p.inscrits, field, reverse) 129 }, 130 }, 131 } 132 p.inscrits.Init() 133 134 p.toInscrits = basic.Button("Passer en liste principale") 135 p.toInscrits.SetObjectName(basic.ONAction) 136 p.toInscrits.ConnectClicked(func(_ bool) { 137 part := ct.Etat.IdParticipant 138 if part == nil { 139 return 140 } 141 ct.MoveToInscrits(part) 142 }) 143 p.toInscrits.SetEnabled(false) 144 145 rH := widgets.NewQHBoxLayout() 146 rH.AddWidget(p.toInscrits, 1, 0) 147 p.attente = lists.Table{ 148 Liste: lists.Liste{ 149 Headers: ct.HeaderAttente, 150 OnClick: func(acces rd.Item, _ int) { 151 p.onClick(acces, false) 152 }, 153 OnDoubleClick: func(acces rd.Item, _ int) { 154 p.onDoubleClick(acces) 155 }, 156 Title: "Liste d'attente", 157 RightHeader: rH, 158 OnRightClick: func(acces rd.Item, pos *core.QPoint) { 159 p.onRightClick(acces, p.attente.MapToGlobal(pos)) 160 }, 161 OnSort: func(field rd.Field, reverse bool) { 162 lists.DefaultSort(p.attente, field, reverse) 163 }, 164 }, 165 } 166 p.attente.Init() 167 168 p.stats = common.NewStatistics(true) 169 lay := widgets.NewQGridLayout(p) 170 lay.AddLayout2(p.camps, 0, 0, 4, 1, 0) 171 lay.AddWidget2(p.stats, 0, 1, 0) 172 lay.AddWidget2(p.inscrits, 1, 1, 0) 173 lay.AddWidget2(p.attente, 3, 1, 0) 174 lay.SetColumnStretch(1, 4) 175 lay.SetRowStretch(1, 3) 176 lay.SetRowStretch(3, 2) 177 return &p 178 } 179 180 func (c *Camps) onClick(acces rd.Item, fromInscrits bool) { 181 c.controller.Etat.IdParticipant = acces.Id 182 c.UpdateToolbar() 183 c.updateToInscrits() 184 if fromInscrits { 185 c.attente.View.ClearSelection() 186 } else { 187 c.inscrits.View.ClearSelection() 188 } 189 } 190 191 func (c *Camps) onDoubleClick(acces rd.Item) { 192 if _, isSimple := acces.Id.(rd.IdParticipantsimple); isSimple { 193 ac := c.controller.Base.NewParticipantsimple(acces.Id.Int64()) 194 modif := EditParticipantsimple(c.controller.Base, c.controller.ParticipantsRight, ac.RawData(), 195 rd.NewOptionnalId(ac.GetPersonne().Id), 196 rd.NewOptionnalId(ac.GetCamp().Id)) 197 if modif != nil { 198 c.controller.UpdateParticipantsimple(*modif) 199 } 200 } else { 201 ac := c.controller.Base.NewParticipant(acces.Id.Int64()) 202 modif := EditParticipant(c.controller.Base, c.controller.ParticipantsRight, ac.RawData(), 203 rd.NewOptionnalId(ac.GetPersonne().Id), ac.GetCamp().Id) 204 if modif != nil { 205 c.controller.UpdateParticipant(*modif) 206 } 207 } 208 } 209 210 func (c *Camps) onRightClick(acces rd.Item, pos *core.QPoint) { 211 var part dm.Inscrit 212 switch id := acces.Id.(type) { 213 case rd.IdParticipantsimple: 214 part = c.controller.Base.NewParticipantsimple(id.Int64()) 215 case rd.IdParticipant: 216 part = c.controller.Base.NewParticipant(id.Int64()) 217 default: 218 return 219 } 220 menu := widgets.NewQMenu(c) 221 ac := menu.AddAction("Aller à la personne") 222 ac.ConnectTriggered(func(_ bool) { 223 c.controller.Main().Controllers.Personnes.SelectPersonne(part.GetPersonne()) 224 }) 225 226 fac, has := part.GetFacture() 227 if has { 228 ac := menu.AddAction("Aller au dossier") 229 ac.ConnectTriggered(func(_ bool) { 230 c.controller.Main().Controllers.SuiviDossiers.SelectFacture(fac) 231 }) 232 } 233 menu.Exec2(pos, nil) 234 } 235 236 func (c *Camps) creerParticipant() { 237 idCamp := c.controller.Etat.CampCurrent 238 if idCamp == nil { 239 return 240 } 241 camp := c.controller.Base.NewCamp(idCamp.Int64()) 242 if camp.RawData().InscriptionSimple { 243 part := CreeParticipantsimple(c.controller.Base, rd.NewOptionnalId(camp.Id)) 244 if part != nil { 245 c.controller.CreeParticipantsimple(*part) 246 } 247 } else { 248 part := CreeParticipant(c.controller.Base, idCamp.Int64()) 249 if part != nil { 250 c.controller.CreeParticipant(*part) 251 } 252 } 253 } 254 255 func (c *Camps) supprimerParticipant() { 256 c.controller.SupprimeParticipantCourant() 257 } 258 259 func (c *Camps) changerParticipant() { 260 dial := lists.NewRechercheCamp(c.controller.Base) 261 dial.Exec() 262 if dial.Out == nil { 263 return 264 } 265 c.controller.ChangeCamp(dial.Out.Int64()) 266 } 267 func (c *Camps) exporterParticipants() { 268 dial := basic.Dialog2("Exporter les participants") 269 lay := widgets.NewQFormLayout(dial) 270 bus := fields.NewBus(true) 271 272 triGroupe := widgets.NewQCheckBox(nil) 273 withAttente := widgets.NewQCheckBox(nil) 274 valid := basic.Button("Exporter") 275 valid.SetObjectName(basic.ONAction) 276 valid.ConnectClicked(func(_ bool) { 277 dial.Accept() 278 }) 279 280 lay.AddRow3("Filtrer les trajets en bus", bus) 281 lay.AddRow3("Trier par groupe", triGroupe) 282 lay.AddRow3("Inclure la liste d'attente", withAttente) 283 lay.AddRow5(valid) 284 if dial.Exec() == 0 { 285 return 286 } 287 path := c.controller.ExportParticipants(bus.GetData(), triGroupe.IsChecked(), withAttente.IsChecked()) 288 basic.ShowFile(path) 289 } 290 291 func (c *Camps) notifieAttentes() { 292 inscrits, attentes := c.controller.CheckListeAttentes() 293 c.notifieAttentesExt(inscrits, attentes) 294 } 295 296 func (c *Camps) notifieAttentesExt(inscrits, attentes []controllers.DiagnosticListeAttente) { 297 notifAttentes, notifInscrits := notifications.NewList(), notifications.NewList() 298 299 setupList := func(liste []controllers.DiagnosticListeAttente, target notifications.List) { 300 for _, row := range liste { 301 row := row // closure in loop 302 part := row.Participant.RawData() 303 text := fmt.Sprintf("<i>%s</i> - <a href>%s</b>: %s --> <b>%s</b>", 304 row.Participant.GetCamp().RawData().Label(), row.Participant.GetPersonne().RawData().NomPrenom(), 305 part.ListeAttente.Statut, row.Hint) 306 target.AddNotification(text, func() { 307 c.controller.SelectParticipant(row.Participant) 308 }) 309 } 310 } 311 312 // le setup peut être visuellement bloquant 313 basic.Delay(func() { setupList(inscrits, notifInscrits) }) 314 basic.Delay(func() { setupList(attentes, notifAttentes) }) 315 316 d := basic.Dialog("Notifications sur les listes d'attentes") 317 d.Layout().AddWidget(basic.Label(`Les listes suivantes indiquent les participants pour lesquels 318 le statut actuel n'est pas celui conseillé (<i>actuel</i> --> <i>conseillé</i>).`)) 319 320 d.Layout().AddWidget(basic.Label("Participants sur <b>liste d'attente</b> :")) 321 if len(attentes) == 0 { 322 d.Layout().AddWidget(basic.Label("Aucune notification pour les listes d'attentes. <br/>")) 323 } else { 324 d.Layout().AddWidget(notifAttentes) 325 } 326 327 d.Layout().AddWidget(basic.Label("Participants sur <b>liste principale</b> :")) 328 if len(inscrits) == 0 { 329 d.Layout().AddWidget(basic.Label("Aucune notification pour les listes d'inscrits.")) 330 } else { 331 d.Layout().AddWidget(notifInscrits) 332 } 333 334 d.Show() // non modal 335 } 336 337 // CreeParticipant propose la création d'un participant. 338 // En cas d'annulation, out vaut `nil` 339 func CreeParticipant(base *dm.BaseLocale, idCamp int64) *rd.Participant { 340 return EditParticipant(base, true, rd.Participant{}, rd.OptionnalId{}, idCamp) 341 } 342 343 // CreeParticipantsimple propose la création d'un participant à un séjour simple. 344 // En cas d'annulation, out vaut `nil` 345 func CreeParticipantsimple(base *dm.BaseLocale, idCamp rd.OptionnalId) *rd.Participantsimple { 346 return EditParticipantsimple(base, true, rd.Participantsimple{}, rd.OptionnalId{}, idCamp) 347 } 348 349 func EditParticipant(base *dm.BaseLocale, editable bool, participant rd.Participant, 350 idPersonne rd.OptionnalId, idCamp int64) *rd.Participant { 351 d := common.NewFicheParticipant(base, editable, idCamp) 352 d.SetData(participant, idPersonne) 353 dialog := common.NewDetails(d, editable) 354 if dialog.Exec() == 0 { 355 return nil 356 } 357 part := d.GetData() 358 return &part 359 } 360 361 func EditParticipantsimple(base *dm.BaseLocale, editable bool, participant rd.Participantsimple, 362 idPersonne rd.OptionnalId, idCamp rd.OptionnalId) *rd.Participantsimple { 363 d := common.NewFicheParticipantsimple(base, editable) 364 d.ActiveCamp(idCamp.IsNil()) 365 d.SetData(participant, idPersonne, idCamp) 366 dialog := common.NewDetails(&d, editable) 367 if dialog.Exec() == 0 { 368 return nil 369 } 370 part := d.GetData() 371 return &part 372 } 373 374 // ---------------------------- Liste détaillée ---------------------------- 375 type listeCamps struct { 376 *widgets.QVBoxLayout 377 table lists.Table 378 379 controller *controllers.Camps 380 } 381 382 func newListeCamps(ct *controllers.Camps) listeCamps { 383 l := listeCamps{QVBoxLayout: widgets.NewQVBoxLayout(), controller: ct} 384 l.table.Headers = []rd.Header{ 385 {Field: dm.CampLabel, Label: ""}, 386 } 387 l.table.HideHeaders = true 388 l.table.MaxWidth = 200 389 l.table.OnRightClick = func(acces rd.Item, pos *core.QPoint) { 390 menu := widgets.NewQMenu(l.table) 391 acCamp := l.controller.Base.NewCamp(acces.Id.Int64()) 392 if directeur, has := acCamp.GetDirecteur(); has { 393 ac := menu.AddAction("Aller au directeur") 394 ac.ConnectTriggered(func(_ bool) { 395 l.controller.Main().Controllers.Personnes.SelectPersonne(directeur) 396 }) 397 ac = menu.AddAction("Envoyer un mail au directeur...") 398 ac.ConnectTriggered(func(_ bool) { 399 to := string(directeur.RawData().Mail) 400 m := common.NewMailLibre(to, "") 401 if m.Exec() == 0 { 402 return 403 } 404 ct.Main().NotifieDirecteur(to, m.Text.ToHtml()) 405 }) 406 menu.AddSeparator() 407 } 408 ac := menu.AddAction("Détails...") 409 ac.ConnectTriggered(func(_ bool) { 410 l.editCamp(acces.Id.Int64()) 411 }) 412 ac = menu.AddAction("Documents du camp...") 413 ac.ConnectTriggered(func(_ bool) { 414 l.showDocuments(acces.Id.Int64()) 415 }) 416 ac = menu.AddAction("Suivi financier") 417 ac.ConnectTriggered(func(_ bool) { 418 path := l.controller.ExportSuiviFinancier(acces.Id.Int64()) 419 basic.ShowFile(path) 420 }) 421 l.table.ShowContextMenu(menu, pos) 422 } 423 l.table.OnClick = func(acces rd.Item, _ int) { 424 l.controller.Etat.CampCurrent = acces.Id 425 l.controller.Reset() 426 } 427 l.table.Init() 428 l.table.View.(*widgets.QTableView).VerticalHeader().SetSectionResizeMode(widgets.QHeaderView__ResizeToContents) 429 430 // camps terminés 431 cb := widgets.NewQCheckBox2("Afficher les camps terminés", nil) 432 cb.SetChecked(l.controller.Etat.ShowCampsTerminated) 433 cb.ConnectClicked(func(checked bool) { 434 l.controller.Etat.ShowCampsTerminated = checked 435 l.controller.Reset() 436 }) 437 438 l.AddWidget(l.table, 4, 0) 439 l.AddWidget(cb, 0, 0) 440 return l 441 } 442 443 func (l *listeCamps) editCamp(id int64) { 444 up := EditCamp(l.controller.CampsRight, true, false, l.controller.Base.Camps[id]) 445 if up == nil { 446 return 447 } 448 l.controller.UpdateCamp(*up) 449 } 450 451 func EditCamp(editable, minimalEdit, enableInscriptionSimple bool, camp rd.Camp) *rd.Camp { 452 fiche := common.NewFicheCamp(editable, minimalEdit, enableInscriptionSimple) 453 fiche.SetData(camp) 454 det := common.NewDetails(&fiche, editable) 455 if det.Exec() == 0 { 456 return nil 457 } 458 camp = fiche.GetData() 459 return &camp 460 } 461 462 func (l *listeCamps) showDocuments(id int64) { 463 dial := basic.Dialog("Documents") 464 ac := l.controller.Base.NewCamp(id) 465 if ac.RawData().Envois.Locked { 466 dial.Layout().AddWidget(basic.Label("Attention, l'envoi des documents est encore <b>verrouillé</b>. <br/>" + 467 "<i>Le directeur lèvera ce verrou une fois les documents prêts.</i>")) 468 } 469 list := common.NewListePiecesJointes(l.controller.Main(), 470 fmt.Sprintf("Document du %s", ac.RawData().Label()), 471 controllers.GetDocuments(ac)) 472 dial.Layout().AddWidget(list) 473 dial.Exec() 474 }