Update nuisance submission to go to submitted page
This commit is contained in:
parent
c41154a200
commit
2c0bfb9904
31 changed files with 747 additions and 228 deletions
|
|
@ -73,10 +73,15 @@ func AddRoutes(r *mux.Router) {
|
||||||
// Unauthenticated endpoints
|
// Unauthenticated endpoints
|
||||||
district := resource.District(router)
|
district := resource.District(router)
|
||||||
r.Handle("/district", handlerJSONSlice(district.List)).Methods("GET")
|
r.Handle("/district", handlerJSONSlice(district.List)).Methods("GET")
|
||||||
|
r.Handle("/district/{id}", handlerJSON(district.GetByID)).Methods("GET").Name("district.ByIDGet")
|
||||||
geocode := resource.Geocode(router)
|
geocode := resource.Geocode(router)
|
||||||
r.Handle("/geocode/by-gid/{id:.*}", handlerJSON(geocode.ByGID)).Methods("GET")
|
r.Handle("/geocode/by-gid/{id:.*}", handlerJSON(geocode.ByGID)).Methods("GET")
|
||||||
r.Handle("/geocode/reverse", handlerJSONPost(geocode.Reverse)).Methods("POST")
|
r.Handle("/geocode/reverse", handlerJSONPost(geocode.Reverse)).Methods("POST")
|
||||||
r.Handle("/geocode/suggestion", handlerJSONSlice(geocode.SuggestionList)).Methods("GET")
|
r.Handle("/geocode/suggestion", handlerJSONSlice(geocode.SuggestionList)).Methods("GET")
|
||||||
|
publicreport := resource.Publicreport(router)
|
||||||
|
r.Handle("/publicreport/{id}", handlerJSON(publicreport.ByID)).Methods("GET").Name("publicreport.ByIDGet")
|
||||||
|
publicreport_notification := resource.PublicreportNotification(router)
|
||||||
|
r.Handle("/publicreport-notification", handlerJSONPost(publicreport_notification.Create)).Methods("POST")
|
||||||
|
|
||||||
//r.HandleFunc("/district", apiGetDistrict).Methods("GET")
|
//r.HandleFunc("/district", apiGetDistrict).Methods("GET")
|
||||||
r.HandleFunc("/district/{slug}/logo", apiGetDistrictLogo).Methods("GET").Name("district.logo.BySlug")
|
r.HandleFunc("/district/{slug}/logo", apiGetDistrictLogo).Methods("GET").Name("district.logo.BySlug")
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,9 @@ func (o Organization) ServiceRequestRecent(ctx context.Context) ([]*models.Field
|
||||||
func (o Organization) Slug() string {
|
func (o Organization) Slug() string {
|
||||||
return o.model.Slug.GetOr("")
|
return o.model.Slug.GetOr("")
|
||||||
}
|
}
|
||||||
|
func (o Organization) Website() string {
|
||||||
|
return o.model.Website.GetOr("")
|
||||||
|
}
|
||||||
func OrganizationByID(ctx context.Context, id int) (*Organization, error) {
|
func OrganizationByID(ctx context.Context, id int) (*Organization, error) {
|
||||||
org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, int32(id))
|
org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, int32(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,15 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func PublicreportByID(ctx context.Context, report_id string) (*models.PublicreportReport, error) {
|
||||||
|
report, err := models.PublicreportReports.Query(
|
||||||
|
models.SelectWhere.PublicreportReports.PublicID.EQ(report_id),
|
||||||
|
).One(ctx, db.PGInstance.BobDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
func PublicreportInvalid(ctx context.Context, user User, report_id string) error {
|
func PublicreportInvalid(ctx context.Context, user User, report_id string) error {
|
||||||
report, err := reportFromID(ctx, user, report_id)
|
report, err := reportFromID(ctx, user, report_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
72
platform/publicreport_notification.go
Normal file
72
platform/publicreport_notification.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package platform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||||
|
//"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PublicreportNotification struct {
|
||||||
|
Consent bool
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
Notification bool
|
||||||
|
Phone *types.E164
|
||||||
|
ReportID string
|
||||||
|
Subscription bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicreportNotificationCreate(ctx context.Context, pn PublicreportNotification) error {
|
||||||
|
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("begin txn: %w", err)
|
||||||
|
}
|
||||||
|
defer txn.Rollback(ctx)
|
||||||
|
rep, err := models.PublicreportReports.Query(
|
||||||
|
models.SelectWhere.PublicreportReports.PublicID.EQ(pn.ReportID),
|
||||||
|
).One(ctx, db.PGInstance.BobDB)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("find report '%s': %w", pn.ReportID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = report.SaveReporter(ctx, txn, pn.ReportID, pn.Name, pn.Email, pn.Phone, pn.Consent)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("save reporter: %w", err)
|
||||||
|
}
|
||||||
|
if pn.Email != "" {
|
||||||
|
if pn.Subscription {
|
||||||
|
err = report.RegisterSubscriptionEmail(ctx, txn, pn.Email)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("register subscription email: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pn.Notification {
|
||||||
|
err = report.RegisterNotificationEmail(ctx, txn, pn.ReportID, pn.Email)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("register notification email: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pn.Phone != nil {
|
||||||
|
if pn.Subscription {
|
||||||
|
err = report.RegisterSubscriptionPhone(ctx, txn, *pn.Phone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("register subscription phone: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pn.Notification {
|
||||||
|
err = report.RegisterNotificationPhone(ctx, txn, pn.ReportID, *pn.Phone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("register notification phone: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txn.Commit(ctx)
|
||||||
|
PublicReportReporterUpdated(ctx, rep.OrganizationID, pn.ReportID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package report
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrorWithCode struct {
|
|
||||||
code string
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrorWithCode) Code() string {
|
|
||||||
return e.code
|
|
||||||
}
|
|
||||||
func (e *ErrorWithCode) Error() string {
|
|
||||||
return e.message
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInternalError(err error, format string, args ...any) *ErrorWithCode {
|
|
||||||
log.Error().Err(err).Str("format", format).Msg("internal server error")
|
|
||||||
return newErrorWithCode("internal-error", format, args...)
|
|
||||||
}
|
|
||||||
func newErrorWithCode(code string, format string, args ...any) *ErrorWithCode {
|
|
||||||
if len(args) > 0 {
|
|
||||||
return &ErrorWithCode{
|
|
||||||
err: fmt.Errorf(format, args...),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return &ErrorWithCode{
|
|
||||||
code: code,
|
|
||||||
err: nil,
|
|
||||||
message: format,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||||
"github.com/aarondl/opt/omit"
|
"github.com/aarondl/opt/omit"
|
||||||
"github.com/aarondl/opt/omitnull"
|
"github.com/aarondl/opt/omitnull"
|
||||||
"github.com/rs/zerolog/log"
|
//"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DistrictForReport(ctx context.Context, report_id string) (*models.Organization, error) {
|
func DistrictForReport(ctx context.Context, report_id string) (*models.Organization, error) {
|
||||||
|
|
@ -56,14 +56,14 @@ func GenerateReportID() (string, error) {
|
||||||
return builder.String(), nil
|
return builder.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterNotificationEmail(ctx context.Context, txn bob.Executor, report_id string, destination string) *ErrorWithCode {
|
func RegisterNotificationEmail(ctx context.Context, txn bob.Executor, report_id string, destination string) error {
|
||||||
report, e := reportByPublicID(ctx, db.PGInstance.BobDB, report_id)
|
report, e := reportByPublicID(ctx, db.PGInstance.BobDB, report_id)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to find report")
|
return fmt.Errorf("Failed to find report: %w", e)
|
||||||
}
|
}
|
||||||
e = email.EnsureInDB(ctx, destination)
|
e = email.EnsureInDB(ctx, destination)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to ensure phone is in DB")
|
return fmt.Errorf("Failed to ensure phone is in DB: %w", e)
|
||||||
}
|
}
|
||||||
err := addNotificationEmail(ctx, txn, report, destination)
|
err := addNotificationEmail(ctx, txn, report, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -73,14 +73,14 @@ func RegisterNotificationEmail(ctx context.Context, txn bob.Executor, report_id
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterNotificationPhone(ctx context.Context, txn bob.Executor, report_id string, phone types.E164) *ErrorWithCode {
|
func RegisterNotificationPhone(ctx context.Context, txn bob.Executor, report_id string, phone types.E164) error {
|
||||||
report, e := reportByPublicID(ctx, db.PGInstance.BobDB, report_id)
|
report, e := reportByPublicID(ctx, db.PGInstance.BobDB, report_id)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to find report")
|
return fmt.Errorf("Failed to find report: %w", e)
|
||||||
}
|
}
|
||||||
e = text.EnsureInDB(ctx, db.PGInstance.BobDB, phone)
|
e = text.EnsureInDB(ctx, db.PGInstance.BobDB, phone)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to ensure phone is in DB")
|
return fmt.Errorf("Failed to ensure phone is in DB: %w", e)
|
||||||
}
|
}
|
||||||
err := addNotificationPhone(ctx, txn, report, phone)
|
err := addNotificationPhone(ctx, txn, report, phone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -90,10 +90,10 @@ func RegisterNotificationPhone(ctx context.Context, txn bob.Executor, report_id
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterSubscriptionEmail(ctx context.Context, txn bob.Executor, destination string) *ErrorWithCode {
|
func RegisterSubscriptionEmail(ctx context.Context, txn bob.Executor, destination string) error {
|
||||||
e := email.EnsureInDB(ctx, destination)
|
e := email.EnsureInDB(ctx, destination)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to ensure email is in DB")
|
return fmt.Errorf("Failed to ensure email is in DB: %w", e)
|
||||||
}
|
}
|
||||||
setter := models.PublicreportSubscribeEmailSetter{
|
setter := models.PublicreportSubscribeEmailSetter{
|
||||||
Created: omit.From(time.Now()),
|
Created: omit.From(time.Now()),
|
||||||
|
|
@ -103,16 +103,15 @@ func RegisterSubscriptionEmail(ctx context.Context, txn bob.Executor, destinatio
|
||||||
}
|
}
|
||||||
_, err := models.PublicreportSubscribeEmails.Insert(&setter).Exec(ctx, txn)
|
_, err := models.PublicreportSubscribeEmails.Insert(&setter).Exec(ctx, txn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to save new subscription email row")
|
return fmt.Errorf("Failed to save new subscription email row: %w", err)
|
||||||
return newInternalError(err, "Failed to save new subscription email row")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func RegisterSubscriptionPhone(ctx context.Context, txn bob.Executor, phone types.E164) *ErrorWithCode {
|
func RegisterSubscriptionPhone(ctx context.Context, txn bob.Executor, phone types.E164) error {
|
||||||
e := text.EnsureInDB(ctx, db.PGInstance.BobDB, phone)
|
e := text.EnsureInDB(ctx, db.PGInstance.BobDB, phone)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to ensure phone is in DB")
|
return fmt.Errorf("Failed to ensure phone is in DB: %w", e)
|
||||||
}
|
}
|
||||||
setter := models.PublicreportSubscribePhoneSetter{
|
setter := models.PublicreportSubscribePhoneSetter{
|
||||||
Created: omit.From(time.Now()),
|
Created: omit.From(time.Now()),
|
||||||
|
|
@ -122,16 +121,15 @@ func RegisterSubscriptionPhone(ctx context.Context, txn bob.Executor, phone type
|
||||||
}
|
}
|
||||||
_, err := models.PublicreportSubscribePhones.Insert(&setter).Exec(ctx, txn)
|
_, err := models.PublicreportSubscribePhones.Insert(&setter).Exec(ctx, txn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to save new subscription phone row")
|
return fmt.Errorf("Failed to save new subscription phone row: %w", err)
|
||||||
return newInternalError(err, "Failed to save new subscription phone row")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveReporter(ctx context.Context, txn bob.Executor, report_id string, name string, email string, phone *types.E164, has_consent bool) *ErrorWithCode {
|
func SaveReporter(ctx context.Context, txn bob.Executor, report_id string, name string, email string, phone *types.E164, has_consent bool) error {
|
||||||
report, e := reportByPublicID(ctx, db.PGInstance.BobDB, report_id)
|
report, e := reportByPublicID(ctx, db.PGInstance.BobDB, report_id)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return newInternalError(e, "Failed to find report")
|
return fmt.Errorf("Failed to find report: %w", e)
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
err := updateReporterName(ctx, txn, report, name)
|
err := updateReporterName(ctx, txn, report, name)
|
||||||
|
|
@ -162,7 +160,7 @@ func reportByPublicID(ctx context.Context, txn bob.Executor, public_id string) (
|
||||||
models.SelectWhere.PublicreportReports.PublicID.EQ(public_id),
|
models.SelectWhere.PublicreportReports.PublicID.EQ(public_id),
|
||||||
).One(ctx, txn)
|
).One(ctx, txn)
|
||||||
}
|
}
|
||||||
func addNotificationEmail(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, email string) *ErrorWithCode {
|
func addNotificationEmail(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, email string) error {
|
||||||
setter := models.PublicreportNotifyEmailSetter{
|
setter := models.PublicreportNotifyEmailSetter{
|
||||||
Created: omit.From(time.Now()),
|
Created: omit.From(time.Now()),
|
||||||
Deleted: omitnull.FromPtr[time.Time](nil),
|
Deleted: omitnull.FromPtr[time.Time](nil),
|
||||||
|
|
@ -171,11 +169,11 @@ func addNotificationEmail(ctx context.Context, txn bob.Executor, report *models.
|
||||||
}
|
}
|
||||||
_, err := models.PublicreportNotifyEmails.Insert(&setter).Exec(ctx, txn)
|
_, err := models.PublicreportNotifyEmails.Insert(&setter).Exec(ctx, txn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newInternalError(err, "Failed to save new notification email row")
|
return fmt.Errorf("Failed to save new notification email row: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func addNotificationPhone(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, phone types.E164) *ErrorWithCode {
|
func addNotificationPhone(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, phone types.E164) error {
|
||||||
var err error
|
var err error
|
||||||
setter := models.PublicreportNotifyPhoneSetter{
|
setter := models.PublicreportNotifyPhoneSetter{
|
||||||
Created: omit.From(time.Now()),
|
Created: omit.From(time.Now()),
|
||||||
|
|
@ -185,39 +183,38 @@ func addNotificationPhone(ctx context.Context, txn bob.Executor, report *models.
|
||||||
}
|
}
|
||||||
_, err = models.PublicreportNotifyPhones.Insert(&setter).Exec(ctx, txn)
|
_, err = models.PublicreportNotifyPhones.Insert(&setter).Exec(ctx, txn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newInternalError(err, "Failed to save new notification phone row")
|
return fmt.Errorf("Failed to save new notification phone row: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func updateReporterConsent(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, has_consent bool) *ErrorWithCode {
|
func updateReporterConsent(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, has_consent bool) error {
|
||||||
return updateReportCol(ctx, txn, report, &models.PublicreportReportSetter{
|
return updateReportCol(ctx, txn, report, &models.PublicreportReportSetter{
|
||||||
ReporterContactConsent: omitnull.From(has_consent),
|
ReporterContactConsent: omitnull.From(has_consent),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func updateReporterEmail(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, email string) *ErrorWithCode {
|
func updateReporterEmail(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, email string) error {
|
||||||
return updateReportCol(ctx, txn, report, &models.PublicreportReportSetter{
|
return updateReportCol(ctx, txn, report, &models.PublicreportReportSetter{
|
||||||
ReporterEmail: omit.From(email),
|
ReporterEmail: omit.From(email),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func updateReporterName(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, name string) *ErrorWithCode {
|
func updateReporterName(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, name string) error {
|
||||||
return updateReportCol(ctx, txn, report, &models.PublicreportReportSetter{
|
return updateReportCol(ctx, txn, report, &models.PublicreportReportSetter{
|
||||||
ReporterName: omit.From(name),
|
ReporterName: omit.From(name),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func updateReportCol(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, setter *models.PublicreportReportSetter) *ErrorWithCode {
|
func updateReportCol(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, setter *models.PublicreportReportSetter) error {
|
||||||
err := report.Update(ctx, txn, setter)
|
err := report.Update(ctx, txn, setter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("public_id", report.PublicID).Int32("report_id", report.ID).Msg("Failed to update report")
|
return fmt.Errorf("Failed to update nuisance report in the database: %w", err)
|
||||||
return newInternalError(err, "Failed to update nuisance report in the database")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func updateReporterPhone(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, phone types.E164) *ErrorWithCode {
|
func updateReporterPhone(ctx context.Context, txn bob.Executor, report *models.PublicreportReport, phone types.E164) error {
|
||||||
err := report.Update(ctx, txn, &models.PublicreportReportSetter{
|
err := report.Update(ctx, txn, &models.PublicreportReportSetter{
|
||||||
ReporterPhone: omit.From(phone.PhoneString()),
|
ReporterPhone: omit.From(phone.PhoneString()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newInternalError(err, "Failed to update report: %w", err)
|
return fmt.Errorf("Failed to update report: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SomeReport interface {
|
type SomeReport interface {
|
||||||
addNotificationEmail(context.Context, bob.Executor, string) *ErrorWithCode
|
addNotificationEmail(context.Context, bob.Executor, string) error
|
||||||
addNotificationPhone(context.Context, bob.Executor, types.E164) *ErrorWithCode
|
addNotificationPhone(context.Context, bob.Executor, types.E164) error
|
||||||
districtID(context.Context) *int32
|
districtID(context.Context) *int32
|
||||||
updateReporterConsent(context.Context, bob.Executor, bool) *ErrorWithCode
|
updateReporterConsent(context.Context, bob.Executor, bool) error
|
||||||
updateReporterEmail(context.Context, bob.Executor, string) *ErrorWithCode
|
updateReporterEmail(context.Context, bob.Executor, string) error
|
||||||
updateReporterName(context.Context, bob.Executor, string) *ErrorWithCode
|
updateReporterName(context.Context, bob.Executor, string) error
|
||||||
updateReporterPhone(context.Context, bob.Executor, types.E164) *ErrorWithCode
|
updateReporterPhone(context.Context, bob.Executor, types.E164) error
|
||||||
PublicReportID() string
|
PublicReportID() string
|
||||||
reportID() int32
|
reportID() int32
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/publicreport"
|
pr "github.com/Gleipnir-Technology/nidus-sync/platform/publicreport"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
@ -48,7 +48,7 @@ func toImageURLs(m map[string][]uuid.UUID, id string) []string {
|
||||||
return urls
|
return urls
|
||||||
}
|
}
|
||||||
func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communicationList, *nhttp.ErrorWithStatus) {
|
func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communicationList, *nhttp.ErrorWithStatus) {
|
||||||
reports, err := publicreport.ReportsForOrganization(ctx, user.Organization.ID)
|
reports, err := pr.ReportsForOrganization(ctx, user.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nhttp.NewError("nuisance report query: %w", err)
|
return nil, nhttp.NewError("nuisance report query: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,12 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"net/http"
|
"net/http"
|
||||||
//"github.com/rs/zerolog/log"
|
//"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
@ -17,6 +21,7 @@ type district struct {
|
||||||
PhoneOffice string `json:"phone_office"`
|
PhoneOffice string `json:"phone_office"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
URLLogo string `json:"url_logo"`
|
URLLogo string `json:"url_logo"`
|
||||||
|
URLWebsite string `json:"url_website"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func District(r *router) *districtR {
|
func District(r *router) *districtR {
|
||||||
|
|
@ -25,6 +30,23 @@ func District(r *router) *districtR {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (res *districtR) GetByID(ctx context.Context, r *http.Request, query QueryParams) (*district, *nhttp.ErrorWithStatus) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id_str := vars["id"]
|
||||||
|
id, err := strconv.Atoi(id_str)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewBadRequest("id conversion: %w", err)
|
||||||
|
}
|
||||||
|
org, err := platform.OrganizationByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("get org: %w", err)
|
||||||
|
}
|
||||||
|
district, err := newDistrict(res.router, org)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("new district: %w", err)
|
||||||
|
}
|
||||||
|
return district, nil
|
||||||
|
}
|
||||||
func (res *districtR) List(ctx context.Context, r *http.Request, query QueryParams) ([]*district, *nhttp.ErrorWithStatus) {
|
func (res *districtR) List(ctx context.Context, r *http.Request, query QueryParams) ([]*district, *nhttp.ErrorWithStatus) {
|
||||||
organizations, err := platform.OrganizationList(ctx)
|
organizations, err := platform.OrganizationList(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -32,20 +54,32 @@ func (res *districtR) List(ctx context.Context, r *http.Request, query QueryPara
|
||||||
}
|
}
|
||||||
districts := make([]*district, 0)
|
districts := make([]*district, 0)
|
||||||
for _, org := range organizations {
|
for _, org := range organizations {
|
||||||
slug := org.Slug()
|
district, err := newDistrict(res.router, org)
|
||||||
if slug == "" {
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("make district: %w", err)
|
||||||
|
}
|
||||||
|
if district == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logo, err := res.router.SlugToURI("district.logo.BySlug", slug)
|
districts = append(districts, district)
|
||||||
if err != nil {
|
|
||||||
return nil, nhttp.NewError("logo url: %w", err)
|
|
||||||
}
|
|
||||||
districts = append(districts, &district{
|
|
||||||
Name: org.Name(),
|
|
||||||
PhoneOffice: org.PhoneOffice(),
|
|
||||||
Slug: slug,
|
|
||||||
URLLogo: logo,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return districts, nil
|
return districts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newDistrict(r *router, org *platform.Organization) (*district, error) {
|
||||||
|
slug := org.Slug()
|
||||||
|
if slug == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
logo, err := r.SlugToURI("district.logo.BySlug", slug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("logo url: %w", err)
|
||||||
|
}
|
||||||
|
return &district{
|
||||||
|
Name: org.Name(),
|
||||||
|
PhoneOffice: org.PhoneOffice(),
|
||||||
|
Slug: slug,
|
||||||
|
URLLogo: logo,
|
||||||
|
URLWebsite: org.Website(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ type nuisanceR struct {
|
||||||
router *router
|
router *router
|
||||||
}
|
}
|
||||||
type nuisance struct {
|
type nuisance struct {
|
||||||
ID string `json:"id"`
|
District string `json:"district"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
URI string `json:"uri"`
|
||||||
}
|
}
|
||||||
type nuisanceForm struct {
|
type nuisanceForm struct {
|
||||||
AdditionalInfo string `schema:"additional-info"`
|
AdditionalInfo string `schema:"additional-info"`
|
||||||
|
|
@ -181,7 +183,17 @@ func (res *nuisanceR) Create(ctx context.Context, r *http.Request, n nuisanceFor
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nhttp.NewError("create nuisance report: %w", err)
|
return nil, nhttp.NewError("create nuisance report: %w", err)
|
||||||
}
|
}
|
||||||
|
uri, err := res.router.IDStrToURI("publicreport.ByIDGet", report.PublicID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("generate uri: %w", err)
|
||||||
|
}
|
||||||
|
district_uri, err := res.router.IDToURI("district.ByIDGet", int(report.OrganizationID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("generate district uri: %w", err)
|
||||||
|
}
|
||||||
return &nuisance{
|
return &nuisance{
|
||||||
ID: report.PublicID,
|
District: district_uri,
|
||||||
|
ID: report.PublicID,
|
||||||
|
URI: uri,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
resource/publicreport.go
Normal file
46
resource/publicreport.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||||
|
"net/http"
|
||||||
|
//"github.com/rs/zerolog/log"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type publicreportR struct {
|
||||||
|
router *router
|
||||||
|
}
|
||||||
|
|
||||||
|
type publicreport struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
District string `json:"district"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Publicreport(r *router) *publicreportR {
|
||||||
|
return &publicreportR{
|
||||||
|
router: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *publicreportR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*publicreport, *nhttp.ErrorWithStatus) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
public_id := vars["id"]
|
||||||
|
if public_id == "" {
|
||||||
|
return nil, nhttp.NewBadRequest("You must provid an ID")
|
||||||
|
}
|
||||||
|
report, err := platform.PublicreportByID(ctx, public_id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("get report: %w", err)
|
||||||
|
}
|
||||||
|
district_uri, err := res.router.IDToURI("district.ByIDGet", int(report.OrganizationID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("district uri: %w", err)
|
||||||
|
}
|
||||||
|
return &publicreport{
|
||||||
|
District: district_uri,
|
||||||
|
ID: report.PublicID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
56
resource/publicreport_notification.go
Normal file
56
resource/publicreport_notification.go
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type publicreportNotificationR struct {
|
||||||
|
router *router
|
||||||
|
}
|
||||||
|
|
||||||
|
type publicreportNotification struct {
|
||||||
|
Consent bool `json:"consent"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Notification bool `json:"notification"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
ReportID string `json:"report_id"`
|
||||||
|
Subscription bool `json:"subscription"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicreportNotification(r *router) *publicreportNotificationR {
|
||||||
|
return &publicreportNotificationR{
|
||||||
|
router: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res *publicreportNotificationR) Create(ctx context.Context, r *http.Request, n publicreportNotification) (*publicreportNotification, *nhttp.ErrorWithStatus) {
|
||||||
|
var err error
|
||||||
|
var phone *types.E164
|
||||||
|
if n.Phone != "" {
|
||||||
|
phone, err = text.ParsePhoneNumber(n.Phone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewBadRequest("can't parse phone: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = platform.PublicreportNotificationCreate(ctx, platform.PublicreportNotification{
|
||||||
|
Consent: n.Consent,
|
||||||
|
Email: n.Email,
|
||||||
|
Name: n.Name,
|
||||||
|
Notification: n.Notification,
|
||||||
|
Phone: phone,
|
||||||
|
ReportID: n.ReportID,
|
||||||
|
Subscription: n.Subscription,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("create notification: %w", err)
|
||||||
|
}
|
||||||
|
log.Info().Str("name", n.Name).Str("email", n.Email).Str("phone", n.Phone).Str("report_id", n.ReportID).Msg("Added reporter data")
|
||||||
|
return &n, nil
|
||||||
|
}
|
||||||
|
|
@ -48,11 +48,14 @@ func (r *router) UUIDFromURI(route string, uri string) (*uuid.UUID, error) {
|
||||||
}
|
}
|
||||||
func (r *router) IDToURI(route string, id int) (string, error) {
|
func (r *router) IDToURI(route string, id int) (string, error) {
|
||||||
i := strconv.FormatInt(int64(id), 10)
|
i := strconv.FormatInt(int64(id), 10)
|
||||||
|
return r.IDStrToURI(route, i)
|
||||||
|
}
|
||||||
|
func (r *router) IDStrToURI(route string, id string) (string, error) {
|
||||||
handler := r.router.Get(route)
|
handler := r.router.Get(route)
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
return "", fmt.Errorf("nil handler '%s'", route)
|
return "", fmt.Errorf("nil handler '%s'", route)
|
||||||
}
|
}
|
||||||
uri, err := handler.URL("id", i)
|
uri, err := handler.URL("id", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("build uri: %w", err)
|
return "", fmt.Errorf("build uri: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1 @@
|
||||||
package rmo
|
package rmo
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func postRegisterNotifications(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
has_consent := boolFromForm(r, "consent")
|
|
||||||
has_notification := boolFromForm(r, "notification")
|
|
||||||
has_subscribe := boolFromForm(r, "subscribe")
|
|
||||||
email := r.PostFormValue("email")
|
|
||||||
name := r.PostFormValue("name")
|
|
||||||
phone_str := r.PostFormValue("phone")
|
|
||||||
report_id := r.PostFormValue("report_id")
|
|
||||||
|
|
||||||
var phone *types.E164
|
|
||||||
if phone_str != "" {
|
|
||||||
phone, err = text.ParsePhoneNumber(phone_str)
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=invalid-phone&report=%s", report_id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := r.Context()
|
|
||||||
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to begin transaction")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=transaction-failed&report=%s", report_id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer txn.Rollback(ctx)
|
|
||||||
rep, err := models.PublicreportReports.Query(
|
|
||||||
models.SelectWhere.PublicreportReports.PublicID.EQ(report_id),
|
|
||||||
).One(ctx, db.PGInstance.BobDB)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to get report")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=report-location-failed&report=%s", report_id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e := report.SaveReporter(ctx, txn, report_id, name, email, phone, has_consent)
|
|
||||||
if e != nil {
|
|
||||||
log.Error().Err(e).Str("name", name).Msg("Failed to save reporter")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", e.Code(), report_id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if email != "" {
|
|
||||||
if has_subscribe {
|
|
||||||
e := report.RegisterSubscriptionEmail(ctx, txn, email)
|
|
||||||
if e != nil {
|
|
||||||
log.Error().Err(e).Str("email", email).Msg("Failed to register subscription email")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", e.Code(), report_id), http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if has_notification {
|
|
||||||
e := report.RegisterNotificationEmail(ctx, txn, report_id, email)
|
|
||||||
if e != nil {
|
|
||||||
log.Error().Err(e).Str("email", email).Msg("Failed to register notification email")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", e.Code(), report_id), http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if phone != nil {
|
|
||||||
if has_subscribe {
|
|
||||||
e := report.RegisterSubscriptionPhone(ctx, txn, *phone)
|
|
||||||
if e != nil {
|
|
||||||
log.Error().Err(e).Str("phone", phone_str).Msg("Failed to register subscription phone")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", e.Code(), report_id), http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if has_notification {
|
|
||||||
e := report.RegisterNotificationPhone(ctx, txn, report_id, *phone)
|
|
||||||
if e != nil {
|
|
||||||
log.Error().Err(e).Str("phone", phone_str).Msg("Failed to register notification phone")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/error?code=%s&report=%s", e.Code(), report_id), http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
txn.Commit(ctx)
|
|
||||||
platform.PublicReportReporterUpdated(ctx, rep.OrganizationID, report_id)
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/register-notifications-complete?report=%s", report_id), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -52,5 +52,5 @@ func Router(r *mux.Router) {
|
||||||
r.HandleFunc("/terms-of-service", getTerms).Methods("GET")
|
r.HandleFunc("/terms-of-service", getTerms).Methods("GET")
|
||||||
*/
|
*/
|
||||||
static.AddStaticRoute(r, "/static")
|
static.AddStaticRoute(r, "/static")
|
||||||
r.PathPrefix("/").Handler(static.SinglePageApp("static/gen/rmo"))
|
r.PathPrefix("/").Handler(static.SinglePageApp("static/gen/rmo")).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ func fileFromFilesystem(path string) (*http.File, error) {
|
||||||
// Try to open from local filesystem for development
|
// Try to open from local filesystem for development
|
||||||
fileToServe, err = localFS.Open(path)
|
fileToServe, err = localFS.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("path", path).Msg("Failed to read static file for dev")
|
//log.Warn().Err(err).Str("path", path).Msg("Failed to read static file for dev")
|
||||||
found = false
|
found = false
|
||||||
} else {
|
} else {
|
||||||
found = true
|
found = true
|
||||||
|
|
|
||||||
|
|
@ -39,5 +39,5 @@ func Router(r *mux.Router) {
|
||||||
//r.HandleFunc("/_/*", getRoot)
|
//r.HandleFunc("/_/*", getRoot)
|
||||||
|
|
||||||
static.AddStaticRoute(r, "/static")
|
static.AddStaticRoute(r, "/static")
|
||||||
r.PathPrefix("/").Handler(static.SinglePageApp("static/gen/sync"))
|
r.PathPrefix("/").Handler(static.SinglePageApp("static/gen/sync")).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
//import { useHead } from "@vueuse/head";
|
//import { useHead } from "@vueuse/head";
|
||||||
import { router } from "@/rmo/router";
|
import { router } from "@/rmo/router";
|
||||||
import { useDistrictStore } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import type { District } from "@/type/api";
|
import type { District } from "@/type/api";
|
||||||
|
|
||||||
const district = useDistrictStore();
|
const district = useStoreDistrict();
|
||||||
const count = ref<number>(0);
|
const count = ref<number>(0);
|
||||||
const message = ref<string>("hey");
|
const message = ref<string>("hey");
|
||||||
|
|
||||||
|
|
@ -21,11 +21,11 @@ const increment = (): void => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
district
|
district
|
||||||
.get()
|
.list()
|
||||||
.then((districts: District[]) => {
|
.then((districts: District[]) => {
|
||||||
console.log("got districts");
|
console.log("got districts");
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e: Error) => {
|
||||||
console.error("Failed to get districts", e);
|
console.error("Failed to get districts", e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -535,8 +535,15 @@ import ImageUpload, { Image } from "@/components/ImageUpload.vue";
|
||||||
import MapLocator from "@/components/MapLocator.vue";
|
import MapLocator from "@/components/MapLocator.vue";
|
||||||
import { useGeocodeStore } from "@/store/geocode";
|
import { useGeocodeStore } from "@/store/geocode";
|
||||||
import { useLocationStore } from "@/store/location";
|
import { useLocationStore } from "@/store/location";
|
||||||
|
import { useStorePublicreport } from "@/store/publicreport";
|
||||||
import type { Marker } from "@/types";
|
import type { Marker } from "@/types";
|
||||||
import type { Address, Geocode, GeocodeSuggestion, Location } from "@/type/api";
|
import type {
|
||||||
|
Address,
|
||||||
|
Geocode,
|
||||||
|
GeocodeSuggestion,
|
||||||
|
Location,
|
||||||
|
Publicreport,
|
||||||
|
} from "@/type/api";
|
||||||
import type { Camera } from "@/type/map";
|
import type { Camera } from "@/type/map";
|
||||||
|
|
||||||
const address = ref<string>("");
|
const address = ref<string>("");
|
||||||
|
|
@ -551,6 +558,7 @@ const marker = ref<Marker | null>(null);
|
||||||
const showMore = ref<boolean>(false);
|
const showMore = ref<boolean>(false);
|
||||||
const selectedSuggestion = ref<GeocodeSuggestion | null>(null);
|
const selectedSuggestion = ref<GeocodeSuggestion | null>(null);
|
||||||
const locationStore = useLocationStore();
|
const locationStore = useLocationStore();
|
||||||
|
const storePublicreport = useStorePublicreport();
|
||||||
const geocode = useGeocodeStore();
|
const geocode = useGeocodeStore();
|
||||||
const markers = computed((): Marker[] => {
|
const markers = computed((): Marker[] => {
|
||||||
if (marker.value) {
|
if (marker.value) {
|
||||||
|
|
@ -641,8 +649,9 @@ async function doSubmit() {
|
||||||
body: formData,
|
body: formData,
|
||||||
// Don't set Content-Type, the borwser should do it
|
// Don't set Content-Type, the borwser should do it
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data: Publicreport = (await resp.json()) as Publicreport;
|
||||||
router.push("/complete/" + data.id);
|
storePublicreport.add(data);
|
||||||
|
router.push("/submitted/" + data.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value =
|
errorMessage.value =
|
||||||
error instanceof Error ? error.message : "Upload failed";
|
error instanceof Error ? error.message : "Upload failed";
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import HomeBase from "@/rmo/view/Home.vue";
|
||||||
import HomeDistrict from "@/rmo/view/district/Home.vue";
|
import HomeDistrict from "@/rmo/view/district/Home.vue";
|
||||||
import NuisanceBase from "@/rmo/view/Nuisance.vue";
|
import NuisanceBase from "@/rmo/view/Nuisance.vue";
|
||||||
import NuisanceDistrict from "@/rmo/view/district/Nuisance.vue";
|
import NuisanceDistrict from "@/rmo/view/district/Nuisance.vue";
|
||||||
|
import ReportSubmitted from "@/rmo/view/ReportSubmitted.vue";
|
||||||
import StatusBase from "@/rmo/view/Status.vue";
|
import StatusBase from "@/rmo/view/Status.vue";
|
||||||
import StatusDistrict from "@/rmo/view/district/Status.vue";
|
import StatusDistrict from "@/rmo/view/district/Status.vue";
|
||||||
import Water from "@/rmo/view/Water.vue";
|
import Water from "@/rmo/view/Water.vue";
|
||||||
|
|
@ -106,6 +107,12 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: WaterDistrict,
|
component: WaterDistrict,
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/submitted/:id",
|
||||||
|
name: "ReportSubmitted",
|
||||||
|
component: ReportSubmitted,
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/status",
|
path: "/status",
|
||||||
name: "StatusBase",
|
name: "StatusBase",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,39 @@
|
||||||
import { ref } from "vue";
|
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { District } from "@/type/api";
|
import { ref } from "vue";
|
||||||
|
import type { District } from "@/type/api";
|
||||||
|
|
||||||
export const useDistrictStore = defineStore("district", () => {
|
export const useStoreDistrict = defineStore("district", () => {
|
||||||
const districts = ref<District[] | null>(null);
|
// State
|
||||||
|
const _byURI = ref<Map<string, District>>(new Map());
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const ongoingFetch = ref<Promise<District[]> | null>(null);
|
const ongoingFetch = ref<Promise<District[]> | null>(null);
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
async function byURI(uri: string): Promise<District | undefined> {
|
||||||
|
let district = _byURI.value.get(uri);
|
||||||
|
if (district) {
|
||||||
|
return district;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
const response = await fetch(uri);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const body = await response.json();
|
||||||
|
_byURI.value.set(uri, body);
|
||||||
|
return body;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error loading users:", e);
|
||||||
|
error.value = e instanceof Error ? e.message : "an error ocurred";
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
async function fetchDistricts(): Promise<District[]> {
|
async function fetchDistricts(): Promise<District[]> {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
|
@ -17,7 +43,9 @@ export const useDistrictStore = defineStore("district", () => {
|
||||||
if (!response.ok) throw new Error("Failed to fetch districts");
|
if (!response.ok) throw new Error("Failed to fetch districts");
|
||||||
|
|
||||||
const data: District[] = await response.json();
|
const data: District[] = await response.json();
|
||||||
districts.value = data;
|
data.forEach((d: District) => {
|
||||||
|
_byURI.value.set(d.uri, d);
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e instanceof Error ? e.message : "an error ocurred";
|
error.value = e instanceof Error ? e.message : "an error ocurred";
|
||||||
|
|
@ -27,9 +55,9 @@ export const useDistrictStore = defineStore("district", () => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function get(): Promise<District[]> {
|
async function list(): Promise<District[]> {
|
||||||
if (districts.value != null) {
|
if (_byURI.value.size > 0) {
|
||||||
return districts.value;
|
return Array.from(_byURI.value.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ongoingFetch.value !== null) {
|
if (ongoingFetch.value !== null) {
|
||||||
|
|
@ -37,12 +65,13 @@ export const useDistrictStore = defineStore("district", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = await fetchDistricts();
|
const s = await fetchDistricts();
|
||||||
districts.value = s;
|
|
||||||
ongoingFetch.value = null;
|
ongoingFetch.value = null;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error,
|
// Actions
|
||||||
get,
|
byURI,
|
||||||
|
list,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -30,18 +30,18 @@ body > .container-fluid {
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted, ref } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
|
|
||||||
import { useDistrictStore } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import Intro from "@/rmo/content/compliance/Intro.vue";
|
import Intro from "@/rmo/content/compliance/Intro.vue";
|
||||||
import type { District } from "@/type/api";
|
import type { District } from "@/type/api";
|
||||||
interface Props {
|
interface Props {
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const districtStore = useDistrictStore();
|
const districtStore = useStoreDistrict();
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const district = computedAsync(async (): Promise<District | undefined> => {
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
const districts = await districtStore.get();
|
const districts = await districtStore.list();
|
||||||
return districts.find((district: District) => district.slug == props.slug);
|
return districts.find((district: District) => district.slug == props.slug);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
326
ts/rmo/view/ReportSubmitted.vue
Normal file
326
ts/rmo/view/ReportSubmitted.vue
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
<style scoped>
|
||||||
|
.logo {
|
||||||
|
max-width: 200px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<template>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Success Card -->
|
||||||
|
<div class="card shadow-sm border-success mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3 class="my-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-check-circle-fill me-2"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Report Successfully Submitted
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div class="alert alert-info py-3">
|
||||||
|
<strong>Your Report ID:</strong>
|
||||||
|
<span class="fs-4 fw-bold">{{ id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-3">
|
||||||
|
<i class="bi bi-person-rolodex"></i>
|
||||||
|
Follow-up
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
Please provide your contact information in case the district has
|
||||||
|
any follow-up questions.
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
id="notificationForm"
|
||||||
|
@submit.prevent="handleSubmit"
|
||||||
|
class="needs-validation"
|
||||||
|
>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="name">Name</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i class="bi bi-person-lines-fill"></i>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="formData.name"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="name"
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="Adam Smith"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email Address</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-envelope"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="formData.email"
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="your@email.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="phone" class="form-label">Phone Number</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-phone"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6zM5 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H5z"
|
||||||
|
/>
|
||||||
|
<path d="M8 14a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="formData.phone"
|
||||||
|
type="tel"
|
||||||
|
class="form-control"
|
||||||
|
id="phone"
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="(123) 456-7890"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check mb-3 form-check">
|
||||||
|
<input
|
||||||
|
v-model="formData.consent"
|
||||||
|
class="form-check-input"
|
||||||
|
id="consent"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="consent">
|
||||||
|
I consent to being contacted at my email address or phone
|
||||||
|
number above just for the purposes of this report.
|
||||||
|
<i
|
||||||
|
class="bi bi-info-circle-fill text-primary ms-1"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-placement="top"
|
||||||
|
title="We will never sell your information. We'll send you notifications, but only if you ask us to. We'll share your information with the district that is in change of mosquito control in the area you've reported, but not with anybody else."
|
||||||
|
></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check mb-3 form-check">
|
||||||
|
<input
|
||||||
|
v-model="formData.notification"
|
||||||
|
class="form-check-input"
|
||||||
|
id="notification"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="notification">
|
||||||
|
I'd like to get updates about my report as it gets handled.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check mb-3 form-check">
|
||||||
|
<input
|
||||||
|
v-model="formData.subscribe"
|
||||||
|
class="form-check-input"
|
||||||
|
id="subscribe"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="subscribe">
|
||||||
|
<template v-if="!district">
|
||||||
|
I'd like to subscribe to periodic updates from my mosquito
|
||||||
|
control district.
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
I'd like to subscribe to periodic updates from
|
||||||
|
{{ district.name }}
|
||||||
|
</template>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
Update report
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4" />
|
||||||
|
|
||||||
|
<!-- Status Check Section -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<h4 class="mb-3">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-search me-2"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Check Your Report Status
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
You can check the status of your report at any time using your
|
||||||
|
Report ID.
|
||||||
|
</p>
|
||||||
|
<a :href="`/status/${id}`" class="btn btn-outline-primary">
|
||||||
|
Check Status
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div v-if="district" class="mb-4 text-center">
|
||||||
|
<p>Your report will be handled by</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ district.name }}</b>
|
||||||
|
</p>
|
||||||
|
<a :href="district.url_website">
|
||||||
|
<img
|
||||||
|
class="logo"
|
||||||
|
:src="district.url_logo"
|
||||||
|
:alt="district.name + 'logo'"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Links -->
|
||||||
|
<div class="text-center">
|
||||||
|
<RouterLink to="/nuisance" class="btn btn-outline-success">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-plus-circle me-1"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Submit Another Report
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { computedAsync } from "@vueuse/core";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
|
import { useStorePublicreport } from "@/store/publicreport";
|
||||||
|
import type { District, Publicreport } from "@/type/api";
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
consent: boolean;
|
||||||
|
notification: boolean;
|
||||||
|
subscribe: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const formData = ref<FormData>({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
consent: true,
|
||||||
|
notification: false,
|
||||||
|
subscribe: false,
|
||||||
|
});
|
||||||
|
const router = useRouter();
|
||||||
|
const storeDistrict = useStoreDistrict();
|
||||||
|
const storePublicreport = useStorePublicreport();
|
||||||
|
|
||||||
|
const report = computedAsync(async (): Promise<Publicreport | undefined> => {
|
||||||
|
return await storePublicreport.byID(props.id);
|
||||||
|
});
|
||||||
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
|
if (!(report.value && report.value.district)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return await storeDistrict.byURI(report.value.district);
|
||||||
|
});
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/publicreport-notification", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
report_id: props.id,
|
||||||
|
...formData.value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Handle success (e.g., show a success message)
|
||||||
|
console.log("Form submitted successfully");
|
||||||
|
} else {
|
||||||
|
// Handle error
|
||||||
|
console.error("Form submission failed");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error submitting form:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
</script>
|
||||||
|
|
@ -46,17 +46,17 @@ import { ref } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
import Home from "@/rmo/content/Home.vue";
|
import Home from "@/rmo/content/Home.vue";
|
||||||
import type { District } from "@/type/api";
|
import type { District } from "@/type/api";
|
||||||
import { useDistrictStore } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const districtStore = useDistrictStore();
|
const districtStore = useStoreDistrict();
|
||||||
|
|
||||||
const district = computedAsync(async (): Promise<District | undefined> => {
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
const districts = await districtStore.get();
|
const districts = await districtStore.list();
|
||||||
return districts.find((district: District) => district.slug == props.slug);
|
return districts.find((district: District) => district.slug == props.slug);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,17 @@ import { ref } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
import Nuisance from "@/rmo/content/Nuisance.vue";
|
import Nuisance from "@/rmo/content/Nuisance.vue";
|
||||||
import type { District } from "@/type/api";
|
import type { District } from "@/type/api";
|
||||||
import { useDistrictStore } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const districtStore = useDistrictStore();
|
const districtStore = useStoreDistrict();
|
||||||
|
|
||||||
const district = computedAsync(async (): Promise<District | undefined> => {
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
const districts = await districtStore.get();
|
const districts = await districtStore.list();
|
||||||
return districts.find((district: District) => district.slug == props.slug);
|
return districts.find((district: District) => district.slug == props.slug);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,17 @@ import { ref } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
import Status from "@/rmo/content/Status.vue";
|
import Status from "@/rmo/content/Status.vue";
|
||||||
import type { District } from "@/type/api";
|
import type { District } from "@/type/api";
|
||||||
import { useDistrictStore } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const districtStore = useDistrictStore();
|
const storeDistrict = useStoreDistrict();
|
||||||
|
|
||||||
const district = computedAsync(async (): Promise<District | undefined> => {
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
const districts = await districtStore.get();
|
const districts = await storeDistrict.list();
|
||||||
return districts.find((district: District) => district.slug == props.slug);
|
return districts.find((district: District) => district.slug == props.slug);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,17 @@ import { ref } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
import Water from "@/rmo/content/Water.vue";
|
import Water from "@/rmo/content/Water.vue";
|
||||||
import type { District } from "@/type/api";
|
import type { District } from "@/type/api";
|
||||||
import { useDistrictStore } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const districtStore = useDistrictStore();
|
const districtStore = useStoreDistrict();
|
||||||
|
|
||||||
const district = computedAsync(async (): Promise<District | undefined> => {
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
const districts = await districtStore.get();
|
const districts = await districtStore.list();
|
||||||
return districts.find((district: District) => district.slug == props.slug);
|
return districts.find((district: District) => district.slug == props.slug);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
40
ts/store/publicreport.ts
Normal file
40
ts/store/publicreport.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import type { Publicreport } from "@/type/api";
|
||||||
|
|
||||||
|
export const useStorePublicreport = defineStore("publicreport", () => {
|
||||||
|
// State
|
||||||
|
const _byID = ref<Map<string, Publicreport>>(new Map());
|
||||||
|
const error = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
//const ongoingFetch = ref<Promise<Publicreport[]> | null>(null);
|
||||||
|
|
||||||
|
function add(pr: Publicreport) {
|
||||||
|
_byID.value.set(pr.id, pr);
|
||||||
|
}
|
||||||
|
// Actions
|
||||||
|
async function byID(id: string): Promise<Publicreport | undefined> {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
const url = `/api/publicreport/${id}`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const body = await response.json();
|
||||||
|
_byID.value.set(id, body);
|
||||||
|
return body;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error loading users:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Actions
|
||||||
|
add,
|
||||||
|
byID,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref, computed } from "vue";
|
import { ref } from "vue";
|
||||||
import { Signal } from "../types";
|
import { Signal } from "@/types";
|
||||||
import { SSEManager, type SSEMessage } from "../SSEManager";
|
import { SSEManager, type SSEMessage } from "@/SSEManager";
|
||||||
import { useSessionStore } from "@/store/session";
|
import { useSessionStore } from "@/store/session";
|
||||||
|
|
||||||
export const useSignalStore = defineStore("signal", () => {
|
export const useSignalStore = defineStore("signal", () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { Session, User } from "@/types";
|
import { User } from "@/types";
|
||||||
import { SSEManager, type SSEMessage } from "@/SSEManager";
|
import { SSEManager, type SSEMessage } from "@/SSEManager";
|
||||||
import { useSessionStore } from "@/store/session";
|
import { useSessionStore } from "@/store/session";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ export interface District {
|
||||||
name: string;
|
name: string;
|
||||||
phone_office: string;
|
phone_office: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
uri: string;
|
||||||
url_logo: string;
|
url_logo: string;
|
||||||
|
url_website: string;
|
||||||
}
|
}
|
||||||
export interface Location {
|
export interface Location {
|
||||||
latitude: number;
|
latitude: number;
|
||||||
|
|
@ -30,3 +32,8 @@ export interface Geocode {
|
||||||
cell: number;
|
cell: number;
|
||||||
location: Location;
|
location: Location;
|
||||||
}
|
}
|
||||||
|
export interface Publicreport {
|
||||||
|
district: string;
|
||||||
|
id: string;
|
||||||
|
uri: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue