WIP: creating contact resource
Some checks failed
/ golint (push) Has been cancelled

This commit is contained in:
Eli Ribble 2026-05-15 20:10:14 +00:00
parent 8b203908a0
commit 725945d95c
No known key found for this signature in database
22 changed files with 381 additions and 135 deletions

View file

@ -30,7 +30,7 @@ type handlerFunctionDelete func(context.Context, *http.Request, platform.User) *
type handlerFunctionGet[T any] func(context.Context, *http.Request, resource.QueryParams) (*T, *nhttp.ErrorWithStatus)
type handlerFunctionGetAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (T, *nhttp.ErrorWithStatus)
type handlerFunctionGetImage func(context.Context, *http.Request, platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus)
type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]T, *nhttp.ErrorWithStatus)
type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]T, *nhttp.ErrorWithStatus)
type handlerFunctionPost[RequestType any, ResponseType any] func(context.Context, *http.Request, RequestType) (ResponseType, *nhttp.ErrorWithStatus)
type handlerFunctionPostAuthenticated[RequestType any, ResponseType any] func(context.Context, *http.Request, platform.User, RequestType) (ResponseType, *nhttp.ErrorWithStatus)

View file

@ -48,6 +48,7 @@ func AddRoutesRMO(r *mux.Router) {
func AddRoutesSync(r *mux.Router) {
router := resource.NewRouter(r)
contact := resource.Contact(router)
compliance_request := resource.ComplianceRequest(router)
district := resource.District(router)
geocode := resource.Geocode(router)
@ -107,6 +108,8 @@ func AddRoutesSync(r *mux.Router) {
r.Handle("/compliance-request/mailer", authenticatedHandlerJSONPost(compliance_request.CreateMailer)).Methods("POST")
//r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET")
r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST")
r.Handle("/contact", authenticatedHandlerJSONSlice(contact.List)).Methods("GET")
r.Handle("/contact/{id}", authenticatedHandlerJSON(contact.ByIDGet)).Methods("GET").Name("contact.ByIDGet")
email := resource.Email(router)
r.Handle("/email/{id}", authenticatedHandlerJSON(email.Get)).Methods("GET").Name("email.ByIDGet")
r.Handle("/events", auth.NewEnsureAuth(streamEvents)).Methods("GET")

View file

@ -35,13 +35,10 @@ func ContactUpdateName(ctx context.Context, txn db.Ex, id int64, name string) er
return db.ExecuteNoneTx(ctx, txn, statement)
}
/*
func ContactsFromAddress(ctx context.Context, address string) ([]model.Contact, error) {
func ContactsFromOrganizationID(ctx context.Context, txn db.Ex, org_id int64) ([]model.Contact, error) {
statement := table.Contact.SELECT(
table.Contact.AllColumns,
).FROM(table.Contact).
WHERE(table.Contact.Source.EQ(postgres.String(address)).OR(
table.Contact.Destination.EQ(postgres.String(address))))
return db.ExecuteMany[model.Contact](ctx, statement)
WHERE(table.Contact.OrganizationID.EQ(postgres.Int(org_id)))
return db.ExecuteManyTx[model.Contact](ctx, txn, statement)
}
*/

View file

@ -0,0 +1,52 @@
package comms
import (
"context"
"fmt"
//"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/nidus-sync/db"
//"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/enum"
"github.com/Gleipnir-Technology/jet/postgres"
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/comms/model"
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/comms/table"
)
func ContactEmailInsert(ctx context.Context, txn db.Ex, m model.ContactEmail) (model.ContactEmail, error) {
statement := table.ContactEmail.INSERT(table.ContactEmail.MutableColumns).
MODEL(m).
RETURNING(table.ContactEmail.AllColumns)
return db.ExecuteOneTx[model.ContactEmail](ctx, txn, statement)
}
func ContactEmailFromAddress(ctx context.Context, txn db.Ex, address string) (model.ContactEmail, error) {
statement := table.ContactEmail.SELECT(
table.ContactEmail.AllColumns,
).FROM(table.ContactEmail).
WHERE(table.ContactEmail.Address.EQ(postgres.String(address)))
return db.ExecuteOneTx[model.ContactEmail](ctx, txn, statement)
}
func ContactEmailByContactIDs(ctx context.Context, txn db.Ex, contact_ids []int64) (result map[int64][]model.ContactEmail, err error) {
sql_ids := make([]postgres.Expression, len(contact_ids))
for i, contact_id := range contact_ids {
sql_ids[i] = postgres.Int(contact_id)
}
statement := table.ContactEmail.SELECT(
table.ContactEmail.AllColumns,
).FROM(table.ContactEmail).
WHERE(table.ContactEmail.ContactID.IN(sql_ids...))
rows, err := db.ExecuteManyTx[model.ContactEmail](ctx, txn, statement)
if err != nil {
return result, fmt.Errorf("query by contact IDs: %w", err)
}
for _, contact_id := range contact_ids {
result[contact_id] = make([]model.ContactEmail, 0)
}
for _, row := range rows {
id := int64(row.ContactID)
cur := result[id]
cur = append(cur, row)
result[id] = cur
}
return result, nil
}

View file

@ -2,6 +2,7 @@ package comms
import (
"context"
"fmt"
//"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/nidus-sync/db"
@ -46,14 +47,27 @@ func ContactPhoneUpdateStopMessageID(ctx context.Context, txn db.Ex, e164 string
WHERE(table.ContactPhone.E164.EQ(postgres.String(e164)))
return db.ExecuteNoneTx(ctx, txn, statement)
}
/*
func ContactPhonesFromAddress(ctx context.Context, address string) ([]model.ContactPhone, error) {
func ContactPhoneByContactIDs(ctx context.Context, txn db.Ex, contact_ids []int64) (result map[int64][]model.ContactPhone, err error) {
sql_ids := make([]postgres.Expression, len(contact_ids))
for i, contact_id := range contact_ids {
sql_ids[i] = postgres.Int(contact_id)
}
statement := table.ContactPhone.SELECT(
table.ContactPhone.AllColumns,
).FROM(table.ContactPhone).
WHERE(table.ContactPhone.Source.EQ(postgres.String(address)).OR(
table.ContactPhone.Destination.EQ(postgres.String(address))))
return db.ExecuteMany[model.ContactPhone](ctx, statement)
WHERE(table.ContactPhone.ContactID.IN(sql_ids...))
rows, err := db.ExecuteManyTx[model.ContactPhone](ctx, txn, statement)
if err != nil {
return result, fmt.Errorf("query by contact IDs: %w", err)
}
for _, contact_id := range contact_ids {
result[contact_id] = make([]model.ContactPhone, 0)
}
for _, row := range rows {
id := int64(row.ContactID)
cur := result[id]
cur = append(cur, row)
result[id] = cur
}
return result, nil
}
*/

70
db/query/public/mailer.go Normal file
View file

@ -0,0 +1,70 @@
package public
import (
"context"
"errors"
"fmt"
"github.com/Gleipnir-Technology/jet/postgres"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/table"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
)
func mailerBaseQuery() postgres.SelectStatement {
return table.ComplianceReportRequest.SELECT(
table.Address.AllColumns,
table.ComplianceReportRequest.AllColumns,
).FROM(
table.ComplianceReportRequest,
).FROM(
table.ComplianceReportRequest.INNER_JOIN(
table.ComplianceReportRequestMailer,
table.ComplianceReportRequestMailer.ComplianceReportRequestID.EQ(
table.ComplianceReportRequest.ID),
),
).FROM(
table.ComplianceReportRequest.INNER_JOIN(
table.Lead,
table.Lead.ID.EQ(
table.ComplianceReportRequest.LeadID,
),
),
).FROM(
table.Lead.INNER_JOIN(
table.Site,
table.Site.ID.EQ(
table.Lead.SiteID,
),
),
).FROM(
table.Site.INNER_JOIN(
table.Address,
table.Address.ID.EQ(
table.Site.AddressID,
),
),
)
}
func MailerFromPublicID(ctx context.Context, txn db.Ex, org_id int64, id int64) (*types.Mailer, error) {
statement := mailerBaseQuery().WHERE(
table.ComplianceReportRequest.ID.EQ(postgres.Int(id)).AND(
table.Site.OrganizationID.EQ(postgres.Int(org_id))),
)
row, err := db.ExecuteOneTx[types.Mailer](ctx, txn, statement)
if err != nil {
if errors.Is(err, db.ErrNoRows) {
return nil, nil
}
return nil, fmt.Errorf("query: %w", err)
}
return &row, nil
}
func MailersFromOrganizationID(ctx context.Context, txn db.Ex, org_id int64, limit int64) ([]types.Mailer, error) {
statement := mailerBaseQuery().WHERE(
table.Site.OrganizationID.EQ(postgres.Int(org_id)),
).ORDER_BY(
table.ComplianceReportRequest.Created,
).LIMIT(limit)
return db.ExecuteManyTx[types.Mailer](ctx, txn, statement)
}

54
platform/contact.go Normal file
View file

@ -0,0 +1,54 @@
package platform
import (
"context"
"fmt"
"github.com/Gleipnir-Technology/nidus-sync/db"
querycomms "github.com/Gleipnir-Technology/nidus-sync/db/query/comms"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
)
func ContactsForOrganization(ctx context.Context, org_id int32) (results []types.Contact, err error) {
txn := db.PGInstance.PGXPool
rows, err := querycomms.ContactsFromOrganizationID(ctx, txn, int64(org_id))
if err != nil {
return results, fmt.Errorf("contacts from organization id: %w", err)
}
contact_ids := make([]int64, len(rows))
for i, row := range rows {
contact_ids[i] = int64(row.ID)
}
contact_emails_by_contact_id, err := querycomms.ContactEmailByContactIDs(ctx, txn, contact_ids)
if err != nil {
return results, fmt.Errorf("by contact ids: %w", err)
}
contact_phones_by_contact_id, err := querycomms.ContactPhoneByContactIDs(ctx, txn, contact_ids)
if err != nil {
return results, fmt.Errorf("by contact ids: %w", err)
}
results = make([]types.Contact, len(rows))
for i, row := range rows {
contact_emails := contact_emails_by_contact_id[int64(row.ID)]
emails := make([]string, len(contact_emails))
for i, e := range contact_emails {
emails[i] = e.Address
}
contact_phones := contact_phones_by_contact_id[int64(row.ID)]
phones := make([]types.Phone, len(contact_phones))
for i, p := range contact_phones {
phones[i] = types.Phone{
E164: p.E164,
CanSMS: p.CanSms,
}
}
results[i] = types.Contact{
Emails: emails,
ID: row.ID,
Name: row.Name,
Phones: phones,
}
}
return results, nil
}

View file

@ -2,7 +2,6 @@ package platform
import (
"context"
"fmt"
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
@ -10,34 +9,12 @@ import (
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
querypublic "github.com/Gleipnir-Technology/nidus-sync/db/query/public"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
"github.com/stephenafamo/scan"
)
func MailerByID(ctx context.Context, user User, id int32) (*types.Mailer, error) {
query := mailerQuery()
query.Apply(
sm.Where(models.ComplianceReportRequests.Columns.ID.EQ(psql.Arg(id))),
sm.Where(
models.Sites.Columns.OrganizationID.EQ(psql.Arg(user.Organization.ID)),
),
)
mailers, err := mailerQueryToRows(ctx, query)
if err != nil {
return nil, err
}
return mailers[id], nil
}
func MailerList(ctx context.Context, user User, limit int) ([]*types.Mailer, error) {
query := mailerQuery()
query.Apply(
sm.Where(
models.Sites.Columns.OrganizationID.EQ(psql.Arg(user.Organization.ID)),
),
sm.OrderBy(models.ComplianceReportRequests.Columns.Created),
sm.Limit(limit),
)
return mailerQueryToRows(ctx, query)
func MailerList(ctx context.Context, user User, limit int) ([]types.Mailer, error) {
return querypublic.MailersFromOrganizationID(ctx, db.PGInstance.PGXPool, int64(user.Organization.ID), int64(limit))
}
func mailerQuery() bob.BaseQuery[*dialect.SelectQuery] {
return psql.Select(
@ -78,11 +55,3 @@ func mailerQuery() bob.BaseQuery[*dialect.SelectQuery] {
),
)
}
func mailerQueryToRows(ctx context.Context, query bob.BaseQuery[*dialect.SelectQuery]) ([]*types.Mailer, error) {
rows, err := bob.All(ctx, db.PGInstance.BobDB, query, scan.StructMapper[*types.Mailer]())
if err != nil {
return nil, fmt.Errorf("query mailers: %w", err)
}
return rows, nil
}

View file

@ -143,13 +143,13 @@ func reportQueryToRows(ctx context.Context, reports []modelpublicreport.Report,
DistrictID: &row.OrganizationID,
District: nil,
PublicID: row.PublicID,
Reporter: types.Contact{
CanSMS: &row.ReporterPhoneCanSms,
Email: &row.ReporterEmail,
HasEmail: row.ReporterEmail != "",
HasPhone: row.ReporterPhone != "",
Name: row.ReporterName,
Phone: &row.ReporterPhone,
Reporter: types.ContactReporter{
Email: row.ReporterEmail,
Name: row.ReporterName,
Phone: types.PhoneReporter{
CanSMS: row.ReporterPhoneCanSms,
Number: row.ReporterPhone,
},
},
Status: row.Status.String(),
Type: row.ReportType.String(),

View file

@ -1,37 +1,32 @@
package types
import (
"encoding/json"
//"github.com/rs/zerolog/log"
//"github.com/rs/zerolog/log"
)
type Contact struct {
CanSMS *bool `db:"can_sms" json:"can_sms"`
Email *string `db:"email" json:"email"`
HasEmail bool `json:"has_email"`
HasPhone bool `json:"has_phone"`
Name string `db:"name" json:"name"`
Phone *string `db:"phone" json:"phone"`
Emails []string `json:"emails"`
ID int32 `json:"-"`
Name string `json:"name"`
Phones []Phone `json:"phones"`
}
type ContactReporter struct {
Email string `json:"email"`
Name string `json:"name"`
Phone PhoneReporter `json:"phone"`
}
type Phone struct {
E164 string `json:"e164"`
CanSMS bool `json:"can_sms"`
}
type PhoneReporter struct {
CanSMS bool `json:"can_sms"`
Number string `json:"number"`
}
func (c Contact) MarshalJSON() ([]byte, error) {
to_marshal := make(map[string]interface{}, 0)
if c.CanSMS != nil {
to_marshal["can_sms"] = *c.CanSMS
}
to_marshal["name"] = c.Name
to_marshal["has_email"] = (c.Email != nil && *c.Email != "")
to_marshal["has_phone"] = (c.Phone != nil && *c.Phone != "")
if c.Email != nil {
to_marshal["email"] = *c.Email
} else {
to_marshal["email"] = ""
}
if c.Phone != nil {
to_marshal["phone"] = *c.Phone
} else {
to_marshal["phone"] = ""
}
//log.Debug().Msg("marshaling contact")
return json.Marshal(to_marshal)
/*
func ContactFromModel(m model.Contact) Contact {
return Contact{
Emails:
}
*/

View file

@ -15,7 +15,7 @@ type PublicReport struct {
DistrictID *int32 `db:"organization_id" json:"-"`
District *string `db:"-" json:"district"`
PublicID string `db:"public_id" json:"public_id"`
Reporter Contact `db:"reporter" json:"reporter"`
Reporter ContactReporter `db:"reporter" json:"reporter"`
Status string `db:"status" json:"status"`
Type string `db:"report_type" json:"type"`
URI string `db:"-" json:"uri"`

View file

@ -40,8 +40,11 @@ func SiteFromModel(s *models.Site) Site {
Notes: s.Notes,
OrganizationID: s.OrganizationID,
Owner: Contact{
Name: s.OwnerName,
Phone: &owner_phone,
Name: s.OwnerName,
Phones: []Phone{Phone{
E164: owner_phone,
CanSMS: false,
}},
},
ResidentOwned: resident_owned,
//ParcelID: s.ParcelID,

48
resource/contact.go Normal file
View file

@ -0,0 +1,48 @@
package resource
import (
"context"
"net/http"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
//"github.com/rs/zerolog/log"
)
type contactR struct {
router *router
}
func Contact(r *router) *contactR {
return &contactR{
router: r,
}
}
type contact struct {
types.Contact
URI string
}
func (res *contactR) ByIDGet(ctx context.Context, r *http.Request, user platform.User, qp QueryParams) (contact, *nhttp.ErrorWithStatus) {
return contact{}, nil
}
func (res *contactR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]contact, *nhttp.ErrorWithStatus) {
contacts, err := platform.ContactsForOrganization(ctx, user.Organization.ID)
if err != nil {
return nil, nhttp.NewError("nuisance report query: %w", err)
}
result := make([]contact, len(contacts))
for i, c := range contacts {
uri, err := res.router.IDToURI("contact.ByIDGet", int(c.ID))
if err != nil {
return nil, nhttp.NewError("contact uri: %w", err)
}
result[i] = contact{
Contact: c,
URI: uri,
}
}
return result, nil
}

View file

@ -61,21 +61,21 @@
</div>
<div class="col-md-6">
<label
v-if="report.reporter.has_email"
v-if="report.reporter.emails.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-envelope"></i>
<a :href="'mailto:' + report.reporter.email">{{
report.reporter.email
<a :href="'mailto:' + report.reporter.emails[0]">{{
report.reporter.emails[0]
}}</a>
</label>
<label
v-if="report.reporter.has_phone"
v-if="report.reporter.phones.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-phone"></i>
<a :href="'tel:+' + report.reporter.phone">{{
report.reporter.phone
<a :href="'tel:+' + report.reporter.phones[0]">{{
report.reporter.phones[0]
}}</a>
</label>
</div>

View file

@ -78,8 +78,8 @@
<div
v-if="
!(
selectedReport?.reporter.has_email ||
selectedReport?.reporter.has_phone
selectedReport?.reporter.emails.length ||
selectedReport?.reporter.phones.length
)
"
class="mb-3"
@ -91,8 +91,8 @@
</div>
<div
v-if="
selectedReport?.reporter.has_email ||
selectedReport?.reporter.has_phone
selectedReport?.reporter.emails.length ||
selectedReport?.reporter.phones.length
"
class="mb-3"
>

View file

@ -87,13 +87,13 @@
</div>
<div class="col-md-6">
<label
v-if="report.owner.has_email"
v-if="report.owner.emails.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-envelope"></i>
</label>
<label
v-if="report.owner.has_phone"
v-if="report.owner.phones.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-phone"></i>

View file

@ -248,10 +248,8 @@ const hasCompleteResponse = computed(() => {
r.images.length > 0 ||
r.permission_type == PermissionType.GRANTED ||
r.reporter.name ||
r.reporter.phone ||
r.reporter.has_phone ||
r.reporter.email ||
r.reporter.has_email
r.reporter.phones.length ||
r.reporter.emails.length
) {
return true;
}

View file

@ -61,7 +61,7 @@
id="contact-phone"
name="phone"
placeholder="(555) 123-4567"
v-model="modelValue.reporter.phone"
v-model="modelValue.reporter.phone.number"
/>
</div>
@ -92,7 +92,7 @@
<div
class="alert alert-primary"
role="alert"
v-if="modelValue.reporter.has_email"
v-if="modelValue.reporter.email != ''"
>
You've already added an email address to this report. If you alter the
email below, it will replace the current email address.

View file

@ -199,7 +199,7 @@
<div
class="summary-value"
v-if="
modelValue.reporter?.phone || modelValue.reporter?.has_phone
modelValue.reporter?.phone || modelValue.reporter?.phone != ''
"
>
{{ modelValue.reporter.phone }}
@ -216,7 +216,7 @@
<div
class="summary-value"
v-if="
modelValue.reporter?.email || modelValue.reporter?.has_email
modelValue.reporter?.email || modelValue.reporter?.email != ''
"
>
{{ modelValue.reporter?.email }}

View file

@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { shallowRef } from "vue";
import { ref, shallowRef } from "vue";
import { SSEManager, SSEMessageResource } from "@/SSEManager";
import { useSessionStore } from "@/store/session";
@ -8,6 +8,8 @@ import { apiClient } from "@/client";
import {
Communication,
type CommunicationDTO,
Contact,
type ContactDTO,
PublicReport,
type PublicReportDTO,
} from "@/type/api";
@ -22,6 +24,7 @@ function createResourceStore<dto, full extends uriHaver>(
from_json: jsonConverter<dto, full>,
) {
const _resourceByURI = shallowRef<Map<string, full>>(new Map());
const _resourceFetchAll = ref<Promise<full[]> | null>(null);
const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
new Map(),
);
@ -33,20 +36,25 @@ function createResourceStore<dto, full extends uriHaver>(
}
});
async function byAll(): Promise<full[]> {
const cur = _resourceFetchAll.value;
if (cur) {
return cur;
}
return fetchAll();
}
async function byID(id: string): Promise<full> {
const uri = uriFromID(id);
const cur = _resourceFetchByURI.value.get(uri);
if (cur) {
return cur;
}
return fetchByID(id);
return byURI(uri);
}
async function byURI(uri: string): Promise<full> {
const cur = _resourceFetchByURI.value.get(uri);
let cur = _resourceFetchByURI.value.get(uri);
if (cur) {
return cur;
}
return fetchByURI(uri);
cur = fetchByURI(uri);
_resourceFetchByURI.value.set(uri, cur);
return cur;
}
async function fetchAll(): Promise<full[]> {
const sessionStore = useSessionStore();
@ -88,9 +96,12 @@ function createResourceStore<dto, full extends uriHaver>(
return `${api_base}/${id}`;
}
return {
byAll,
byID,
byURI,
fetchAll,
fetchByID,
fetchByURI,
loadingURI,
};
}
@ -101,6 +112,11 @@ export const useStoreResource = defineStore("resource", () => {
"/communication",
Communication.fromJSON,
),
contact: createResourceStore<ContactDTO, Contact>(
"sync:contact",
"/contact",
Contact.fromJSON,
),
publicreport: createResourceStore<PublicReportDTO, PublicReport>(
"sync:publicreport",
"/publicreport",

View file

@ -77,28 +77,47 @@ export class Bounds {
}
}
export interface ContactOptions {
can_sms: boolean;
email?: string;
has_email: boolean;
has_phone: boolean;
emails?: string[];
name?: string;
phone?: string;
phones?: Phone[];
uri?: string;
}
export interface Phone {
can_sms: boolean;
e164: string;
}
export interface PhoneReporter {
can_sms: boolean;
number: string;
}
export class Contact {
can_sms: boolean;
email: string;
has_email: boolean;
has_phone: boolean;
emails: string[];
name: string;
phone: string;
phones: Phone[];
uri: string;
constructor(options?: ContactOptions) {
this.can_sms = options?.can_sms ?? false;
this.email = options?.email ?? "";
this.has_email = options?.has_email ?? false;
this.has_phone = options?.has_phone ?? false;
this.emails = options?.emails ?? [];
this.name = options?.name ?? "";
this.phone = options?.phone ?? "";
this.phones = options?.phones ?? [];
this.uri = options?.uri ?? "";
}
static fromJSON(json: ContactDTO): Contact {
return new Contact(json);
}
}
export interface ContactDTO {
name: string;
emails: string[];
phones: Phone[];
uri: string;
}
export class ContactReporter {
constructor(
public name: string,
public email: string,
public phone: Phone,
public uri: string,
) {}
}
export interface District {
name: string;
@ -195,7 +214,7 @@ export interface ComplianceUpdate {
//images?: Image[];
location?: Location;
permission_type?: string;
reporter?: Contact;
reporter?: ContactReporter;
submitted?: string;
//uri: string;
wants_scheduled?: boolean;
@ -212,7 +231,7 @@ export interface PublicReportDTO {
location: Location;
log: LogEntryDTO[];
public_id: string;
reporter: Contact;
reporter: ContactReporter;
status: string;
type: string;
uri: string;
@ -224,7 +243,7 @@ export interface PublicReportUpdate {
images?: Image[];
location?: Location;
public_id?: string;
reporter?: Contact;
reporter?: ContactReporter;
status?: string;
type?: string;
uri?: string;
@ -242,7 +261,7 @@ export interface PublicReportOptions {
location: Location;
log: LogEntry[];
public_id: string;
reporter: Contact;
reporter: ContactReporter;
status: string;
type: string;
uri: string;
@ -254,7 +273,7 @@ export class PublicReport {
images: Image[];
log: LogEntry[];
public_id: string;
reporter: Contact;
reporter: ContactReporter;
status: string;
type: string;
uri: string;
@ -266,7 +285,7 @@ export class PublicReport {
this.images = options?.images ?? [];
this.log = options?.log ?? [];
this.public_id = options?.public_id ?? "";
this.reporter = options?.reporter ?? new Contact();
this.reporter = options?.reporter ?? new ContactReporter();
this.status = options?.status ?? "";
this.type = options?.type ?? "";
this.uri = options?.uri ?? "";

View file

@ -12,8 +12,16 @@
</ThreeColumn>
</template>
<script setup lang="ts">
import { computedAsync } from "@vueuse/core";
import ThreeColumn from "@/components/layout/ThreeColumn.vue";
import ReviewContactColumnAction from "@/components/ReviewContactColumnAction.vue";
import ReviewContactColumnDetail from "@/components/ReviewContactColumnDetail.vue";
import ReviewContactColumnList from "@/components/ReviewContactColumnList.vue";
import { useStoreResource } from "@/store/resource";
const storeResource = useStoreResource();
const contacts = computedAsync(() => {
return storeResource.contact.byAll();
});
</script>