detailing.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package e21_presents
  2. import (
  3. "fmt"
  4. "net/url"
  5. "time"
  6. "github.com/qor5/admin/presets"
  7. "github.com/qor5/admin/presets/actions"
  8. . "github.com/qor5/ui/vuetify"
  9. vx "github.com/qor5/ui/vuetifyx"
  10. "github.com/qor5/web"
  11. h "github.com/theplant/htmlgo"
  12. "gorm.io/gorm"
  13. )
  14. // @snippet_begin(PresetsDetailPageTopNotesSample)
  15. type Note struct {
  16. ID int
  17. SourceType string
  18. SourceID int
  19. Content string
  20. CreatedAt time.Time
  21. UpdatedAt time.Time
  22. }
  23. func PresetsDetailPageTopNotes(b *presets.Builder) (
  24. cust *presets.ModelBuilder,
  25. cl *presets.ListingBuilder,
  26. ce *presets.EditingBuilder,
  27. dp *presets.DetailingBuilder,
  28. db *gorm.DB,
  29. ) {
  30. cust, cl, ce, db = PresetsEditingCustomizationValidation(b)
  31. b.URIPrefix(PresetsDetailPageTopNotesPath)
  32. err := db.AutoMigrate(&Note{})
  33. if err != nil {
  34. panic(err)
  35. }
  36. dp = cust.Detailing("TopNotes")
  37. dp.Field("TopNotes").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  38. mi := field.ModelInfo
  39. cu := obj.(*Customer)
  40. title := cu.Name
  41. if len(title) == 0 {
  42. title = cu.Description
  43. }
  44. var notes []*Note
  45. err := db.Where("source_type = 'Customer' AND source_id = ?", cu.ID).
  46. Order("id DESC").
  47. Find(&notes).Error
  48. if err != nil {
  49. panic(err)
  50. }
  51. dt := vx.DataTable(notes).WithoutHeader(true).LoadMoreAt(2, "Show More")
  52. dt.Column("Content").CellComponentFunc(func(obj interface{}, fieldName string, ctx *web.EventContext) h.HTMLComponent {
  53. n := obj.(*Note)
  54. return h.Td(h.Div(
  55. h.Div(
  56. VIcon("comment").Color("blue").Small(true).Class("pr-2"),
  57. h.Text(n.Content),
  58. ).Class("body-1"),
  59. h.Div(
  60. h.Text(n.CreatedAt.Format("Jan 02,15:04 PM")),
  61. h.Text(" by Felix Sun"),
  62. ).Class("grey--text pl-7 body-2"),
  63. ).Class("my-3"))
  64. })
  65. cusID := fmt.Sprint(cu.ID)
  66. dt.RowMenuItemFuncs(presets.EditDeleteRowMenuItemFuncs(mi, mi.PresetsPrefix()+"/notes", url.Values{"model": []string{"Customer"}, "model_id": []string{cusID}})...)
  67. return vx.Card(
  68. dt,
  69. ).HeaderTitle(title).
  70. Actions(
  71. VBtn("Add Note").
  72. Depressed(true).
  73. Attr("@click",
  74. web.POST().EventFunc(actions.New).
  75. Query("model", "Customer").
  76. Query("model_id", cusID).
  77. URL(mi.PresetsPrefix()+"/notes").
  78. Go(),
  79. ),
  80. ).Class("mb-4")
  81. })
  82. b.Model(&Note{}).
  83. InMenu(false).
  84. Editing("Content").
  85. SetterFunc(func(obj interface{}, ctx *web.EventContext) {
  86. note := obj.(*Note)
  87. note.SourceID = ctx.QueryAsInt("model_id")
  88. note.SourceType = ctx.R.FormValue("model")
  89. })
  90. return
  91. }
  92. const PresetsDetailPageTopNotesPath = "/samples/presets-detail-page-top-notes"
  93. // @snippet_end
  94. // @snippet_begin(PresetsDetailPageDetailsSample)
  95. func PresetsDetailPageDetails(b *presets.Builder) (
  96. cust *presets.ModelBuilder,
  97. cl *presets.ListingBuilder,
  98. ce *presets.EditingBuilder,
  99. dp *presets.DetailingBuilder,
  100. db *gorm.DB,
  101. ) {
  102. cust, cl, ce, dp, db = PresetsDetailPageTopNotes(b)
  103. b.URIPrefix(PresetsDetailPageDetailsPath)
  104. err := db.AutoMigrate(&CreditCard{})
  105. if err != nil {
  106. panic(err)
  107. }
  108. dp = cust.Detailing("TopNotes", "Details")
  109. dp.Field("Details").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  110. mi := field.ModelInfo
  111. cu := obj.(*Customer)
  112. cusID := fmt.Sprint(cu.ID)
  113. var termAgreed string
  114. if cu.TermAgreedAt != nil {
  115. termAgreed = cu.TermAgreedAt.Format("Jan 02,15:04 PM")
  116. }
  117. detail := vx.DetailInfo(
  118. vx.DetailColumn(
  119. vx.DetailField(vx.OptionalText(cu.Name).ZeroLabel("No Name")).Label("Name"),
  120. vx.DetailField(vx.OptionalText(cu.Email).ZeroLabel("No Email")).Label("Email"),
  121. vx.DetailField(vx.OptionalText(cusID).ZeroLabel("No ID")).Label("ID"),
  122. vx.DetailField(vx.OptionalText(cu.CreatedAt.Format("Jan 02,15:04 PM")).ZeroLabel("")).Label("Created"),
  123. vx.DetailField(vx.OptionalText(termAgreed).ZeroLabel("Not Agreed Yet")).Label("Terms Agreed"),
  124. ).Header("ACCOUNT INFORMATION"),
  125. vx.DetailColumn(
  126. vx.DetailField(h.RawHTML(cu.Description)).Label("Description"),
  127. ).Header("DETAILS"),
  128. )
  129. return vx.Card(detail).HeaderTitle("Details").
  130. Actions(
  131. VBtn("Agree Terms").
  132. Depressed(true).Class("mr-2").
  133. Attr("@click", web.POST().
  134. EventFunc(actions.Action).
  135. Query(presets.ParamAction, "AgreeTerms").
  136. Query(presets.ParamID, cusID).
  137. Go(),
  138. ),
  139. VBtn("Update details").
  140. Depressed(true).
  141. Attr("@click", web.POST().
  142. EventFunc(actions.Edit).
  143. Query(presets.ParamOverlay, actions.Dialog).
  144. Query(presets.ParamID, cusID).
  145. URL(mi.PresetsPrefix()+"/customers").
  146. Go(),
  147. ),
  148. ).Class("mb-4")
  149. })
  150. dp.Action("AgreeTerms").UpdateFunc(func(id string, ctx *web.EventContext) (err error) {
  151. if ctx.R.FormValue("Agree") != "true" {
  152. ve := &web.ValidationErrors{}
  153. ve.GlobalError("You must agree the terms")
  154. err = ve
  155. return
  156. }
  157. err = db.Model(&Customer{}).Where("id = ?", id).
  158. Updates(map[string]interface{}{"term_agreed_at": time.Now()}).Error
  159. return
  160. }).ComponentFunc(func(id string, ctx *web.EventContext) h.HTMLComponent {
  161. var alert h.HTMLComponent
  162. if ve, ok := ctx.Flash.(*web.ValidationErrors); ok {
  163. alert = VAlert(h.Text(ve.GetGlobalError())).Border("left").
  164. Type("error").
  165. Elevation(2).
  166. ColoredBorder(true)
  167. }
  168. return h.Components(
  169. alert,
  170. VCheckbox().FieldName("Agree").Value(ctx.R.FormValue("Agree")).Label("Agree the terms"),
  171. )
  172. })
  173. return
  174. }
  175. const PresetsDetailPageDetailsPath = "/samples/presets-detail-page-details"
  176. // @snippet_end
  177. // @snippet_begin(PresetsDetailPageCardsSample)
  178. type CreditCard struct {
  179. ID int
  180. CustomerID int
  181. Number string
  182. ExpireYearMonth string
  183. Name string
  184. Type string
  185. Phone string
  186. Email string
  187. }
  188. func PresetsDetailPageCards(b *presets.Builder) (
  189. cust *presets.ModelBuilder,
  190. cl *presets.ListingBuilder,
  191. ce *presets.EditingBuilder,
  192. dp *presets.DetailingBuilder,
  193. db *gorm.DB,
  194. ) {
  195. cust, cl, ce, dp, db = PresetsDetailPageDetails(b)
  196. b.URIPrefix(PresetsDetailPageCardsPath)
  197. err := db.AutoMigrate(&CreditCard{})
  198. if err != nil {
  199. panic(err)
  200. }
  201. dp = cust.Detailing("TopNotes", "Details", "Cards")
  202. dp.Field("Cards").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
  203. mi := field.ModelInfo
  204. cu := obj.(*Customer)
  205. cusID := fmt.Sprint(cu.ID)
  206. var cards []*CreditCard
  207. err := db.Where("customer_id = ?", cu.ID).Order("id ASC").Find(&cards).Error
  208. if err != nil {
  209. panic(err)
  210. }
  211. dt := vx.DataTable(cards).
  212. WithoutHeader(true).
  213. RowExpandFunc(func(obj interface{}, ctx *web.EventContext) h.HTMLComponent {
  214. card := obj.(*CreditCard)
  215. return vx.DetailInfo(
  216. vx.DetailColumn(
  217. vx.DetailField(vx.OptionalText(card.Name).ZeroLabel("No Name")).Label("Name"),
  218. vx.DetailField(vx.OptionalText(card.Number).ZeroLabel("No Number")).Label("Number"),
  219. vx.DetailField(vx.OptionalText(card.ExpireYearMonth).ZeroLabel("No Expires")).Label("Expires"),
  220. vx.DetailField(vx.OptionalText(card.Type).ZeroLabel("No Type")).Label("Type"),
  221. vx.DetailField(vx.OptionalText(card.Phone).ZeroLabel("No phone provided")).Label("Phone"),
  222. vx.DetailField(vx.OptionalText(card.Email).ZeroLabel("No email provided")).Label("Email"),
  223. ),
  224. )
  225. }).RowMenuItemFuncs(presets.EditDeleteRowMenuItemFuncs(mi, mi.PresetsPrefix()+"/credit-cards", url.Values{"customerID": []string{cusID}})...)
  226. dt.Column("Type")
  227. dt.Column("Number")
  228. dt.Column("ExpireYearMonth")
  229. return vx.Card(dt).HeaderTitle("Cards").
  230. Actions(
  231. VBtn("Add Card").
  232. Depressed(true).
  233. Attr("@click",
  234. web.POST().
  235. EventFunc(actions.New).
  236. Query("customerID", cusID).
  237. URL(mi.PresetsPrefix()+"/credit-cards").
  238. Go(),
  239. ).Class("mb-4"),
  240. )
  241. })
  242. cc := b.Model(&CreditCard{}).
  243. InMenu(false)
  244. ccedit := cc.Editing("ExpireYearMonth", "Phone", "Email").
  245. SetterFunc(func(obj interface{}, ctx *web.EventContext) {
  246. card := obj.(*CreditCard)
  247. card.CustomerID = ctx.QueryAsInt("customerID")
  248. })
  249. ccedit.Creating("Number")
  250. return
  251. }
  252. const PresetsDetailPageCardsPath = "/samples/presets-detail-page-cards"
  253. // @snippet_end