Show nuisance report status
This commit is contained in:
parent
37ce3183ca
commit
b2c24a0438
10 changed files with 133 additions and 82 deletions
|
|
@ -18,20 +18,15 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/email"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/email"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/geocode"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/geocode"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/platform/publicreport"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PublicreportByID(ctx context.Context, report_id string) (*models.PublicreportReport, error) {
|
func PublicreportByID(ctx context.Context, report_id string) (*types.Report, error) {
|
||||||
report, err := models.PublicreportReports.Query(
|
return publicreport.Report(ctx, report_id)
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ WHERE i.id IN (1, 2, 3, 4)
|
||||||
GROUP BY i.id;
|
GROUP BY i.id;
|
||||||
*/
|
*/
|
||||||
// Get all the images that belong to the list of report IDs
|
// Get all the images that belong to the list of report IDs
|
||||||
func loadImagesForReport(ctx context.Context, org_id int32, report_ids []int32) (results map[int32][]types.Image, err error) {
|
func loadImagesForReport(ctx context.Context, report_ids []int32) (results map[int32][]types.Image, err error) {
|
||||||
rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select(
|
rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select(
|
||||||
sm.Columns(
|
sm.Columns(
|
||||||
"i.storage_uuid AS uuid",
|
"i.storage_uuid AS uuid",
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReportsForOrganization(ctx context.Context, org_id int32) ([]*types.Report, error) {
|
func ReportsForOrganization(ctx context.Context, org_id int32) ([]*types.Report, error) {
|
||||||
query := reportQuery(org_id)
|
query := reportQuery()
|
||||||
query.Apply(
|
query.Apply(
|
||||||
|
sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))),
|
||||||
sm.Where(psql.Quote("publicreport", "report", "reviewed").IsNull()),
|
sm.Where(psql.Quote("publicreport", "report", "reviewed").IsNull()),
|
||||||
)
|
)
|
||||||
return reportQueryToRows(ctx, org_id, query)
|
return reportQueryToRows(ctx, query)
|
||||||
}
|
}
|
||||||
func reportQueryToRows(ctx context.Context, org_id int32, query bob.BaseQuery[*dialect.SelectQuery]) ([]*types.Report, error) {
|
func reportQueryToRows(ctx context.Context, query bob.BaseQuery[*dialect.SelectQuery]) ([]*types.Report, error) {
|
||||||
rows, err := bob.All(ctx, db.PGInstance.BobDB, query, scan.StructMapper[types.Report]())
|
rows, err := bob.All(ctx, db.PGInstance.BobDB, query, scan.StructMapper[types.Report]())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -34,7 +35,7 @@ func reportQueryToRows(ctx context.Context, org_id int32, query bob.BaseQuery[*d
|
||||||
for i, row := range rows {
|
for i, row := range rows {
|
||||||
report_ids[i] = row.ID
|
report_ids[i] = row.ID
|
||||||
}
|
}
|
||||||
images_by_id, err := loadImagesForReport(ctx, org_id, report_ids)
|
images_by_id, err := loadImagesForReport(ctx, report_ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("images for report: %w", err)
|
return nil, fmt.Errorf("images for report: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -69,12 +70,27 @@ func reportQueryToRows(ctx context.Context, org_id int32, query bob.BaseQuery[*d
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
func Reports(ctx context.Context, org_id int32, ids []int32) ([]*types.Report, error) {
|
func Report(ctx context.Context, public_id string) (*types.Report, error) {
|
||||||
query := reportQuery(org_id)
|
query := reportQuery()
|
||||||
query.Apply(
|
query.Apply(
|
||||||
|
sm.Where(psql.Quote("publicreport", "report", "public_id").EQ(psql.Arg(public_id))),
|
||||||
|
)
|
||||||
|
reports, err := reportQueryToRows(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query to rows: %w", err)
|
||||||
|
}
|
||||||
|
if len(reports) != 1 {
|
||||||
|
return nil, fmt.Errorf("reports returned: %d", len(reports))
|
||||||
|
}
|
||||||
|
return reports[0], nil
|
||||||
|
}
|
||||||
|
func Reports(ctx context.Context, org_id int32, ids []int32) ([]*types.Report, error) {
|
||||||
|
query := reportQuery()
|
||||||
|
query.Apply(
|
||||||
|
sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))),
|
||||||
sm.Where(psql.Quote("publicreport", "report", "id").EQ(psql.Any(ids))),
|
sm.Where(psql.Quote("publicreport", "report", "id").EQ(psql.Any(ids))),
|
||||||
)
|
)
|
||||||
return reportQueryToRows(ctx, org_id, query)
|
return reportQueryToRows(ctx, query)
|
||||||
}
|
}
|
||||||
func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) {
|
func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) {
|
||||||
type _Row struct {
|
type _Row struct {
|
||||||
|
|
@ -92,10 +108,11 @@ func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error
|
||||||
}
|
}
|
||||||
return row.Count, nil
|
return row.Count, nil
|
||||||
}
|
}
|
||||||
func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] {
|
func reportQuery() bob.BaseQuery[*dialect.SelectQuery] {
|
||||||
return psql.Select(
|
return psql.Select(
|
||||||
sm.Columns(
|
sm.Columns(
|
||||||
"address_country AS \"address.country\"",
|
"address_country AS \"address.country\"",
|
||||||
|
"address_gid AS \"address.gid\"",
|
||||||
"address_locality AS \"address.locality\"",
|
"address_locality AS \"address.locality\"",
|
||||||
"address_number AS \"address.number\"",
|
"address_number AS \"address.number\"",
|
||||||
"address_postal_code AS \"address.postal_code\"",
|
"address_postal_code AS \"address.postal_code\"",
|
||||||
|
|
@ -106,6 +123,7 @@ func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] {
|
||||||
"id",
|
"id",
|
||||||
"COALESCE(ST_Y(location::geometry::geometry(point, 4326)), 0.0) AS \"location.latitude\"",
|
"COALESCE(ST_Y(location::geometry::geometry(point, 4326)), 0.0) AS \"location.latitude\"",
|
||||||
"COALESCE(ST_X(location::geometry::geometry(point, 4326)), 0.0) AS \"location.longitude\"",
|
"COALESCE(ST_X(location::geometry::geometry(point, 4326)), 0.0) AS \"location.longitude\"",
|
||||||
|
"organization_id",
|
||||||
"public_id",
|
"public_id",
|
||||||
"report_type",
|
"report_type",
|
||||||
"reporter_email AS \"reporter.email\"",
|
"reporter_email AS \"reporter.email\"",
|
||||||
|
|
@ -114,6 +132,5 @@ func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] {
|
||||||
"status",
|
"status",
|
||||||
),
|
),
|
||||||
sm.From("publicreport.report"),
|
sm.From("publicreport.report"),
|
||||||
sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,12 @@ type Report struct {
|
||||||
Location *Location `db:"location" json:"location"`
|
Location *Location `db:"location" json:"location"`
|
||||||
Log []LogEntry `db:"-" json:"log"`
|
Log []LogEntry `db:"-" json:"log"`
|
||||||
Nuisance *Nuisance `db:"nuisance" json:"nuisance"`
|
Nuisance *Nuisance `db:"nuisance" json:"nuisance"`
|
||||||
|
DistrictID *int32 `db:"organization_id" json:"-"`
|
||||||
|
District *string `db:"-" json:"district"`
|
||||||
PublicID string `db:"public_id" json:"public_id"`
|
PublicID string `db:"public_id" json:"public_id"`
|
||||||
Reporter Contact `db:"reporter" json:"reporter"`
|
Reporter Contact `db:"reporter" json:"reporter"`
|
||||||
Status string `db:"status" json:"status"`
|
Status string `db:"status" json:"status"`
|
||||||
Type string `db:"report_type" json:"type"`
|
Type string `db:"report_type" json:"type"`
|
||||||
|
URI string `db:"-" json:"uri"`
|
||||||
Water *Water `db:"water" json:"water"`
|
Water *Water `db:"water" json:"water"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type district struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
PhoneOffice string `json:"phone_office"`
|
PhoneOffice string `json:"phone_office"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
|
URI string `json:"uri"`
|
||||||
URLLogo string `json:"url_logo"`
|
URLLogo string `json:"url_logo"`
|
||||||
URLWebsite string `json:"url_website"`
|
URLWebsite string `json:"url_website"`
|
||||||
}
|
}
|
||||||
|
|
@ -75,10 +76,15 @@ func newDistrict(r *router, org *platform.Organization) (*district, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("logo url: %w", err)
|
return nil, fmt.Errorf("logo url: %w", err)
|
||||||
}
|
}
|
||||||
|
uri, err := r.IDToURI("district.ByIDGet", int(org.ID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nhttp.NewError("district uri: %w", err)
|
||||||
|
}
|
||||||
return &district{
|
return &district{
|
||||||
Name: org.Name(),
|
Name: org.Name(),
|
||||||
PhoneOffice: org.PhoneOffice(),
|
PhoneOffice: org.PhoneOffice(),
|
||||||
Slug: slug,
|
Slug: slug,
|
||||||
|
URI: uri,
|
||||||
URLLogo: logo,
|
URLLogo: logo,
|
||||||
URLWebsite: org.Website(),
|
URLWebsite: org.Website(),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
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"
|
||||||
|
|
@ -16,25 +15,13 @@ type publicreportR struct {
|
||||||
router *router
|
router *router
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreport struct {
|
|
||||||
Address string `json:"address"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
District string `json:"district"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
ImageCount int `json:"image_count"`
|
|
||||||
Location types.Location `json:"location"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
URI string `json:"uri"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Publicreport(r *router) *publicreportR {
|
func Publicreport(r *router) *publicreportR {
|
||||||
return &publicreportR{
|
return &publicreportR{
|
||||||
router: r,
|
router: r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res *publicreportR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*publicreport, *nhttp.ErrorWithStatus) {
|
func (res *publicreportR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*types.Report, *nhttp.ErrorWithStatus) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
public_id := vars["id"]
|
public_id := vars["id"]
|
||||||
if public_id == "" {
|
if public_id == "" {
|
||||||
|
|
@ -44,27 +31,18 @@ func (res *publicreportR) ByID(ctx context.Context, r *http.Request, query Query
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nhttp.NewError("get report: %w", err)
|
return nil, nhttp.NewError("get report: %w", err)
|
||||||
}
|
}
|
||||||
district_uri, err := res.router.IDToURI("district.ByIDGet", int(report.OrganizationID))
|
var district_uri string
|
||||||
|
if report.DistrictID != nil {
|
||||||
|
district_uri, err = res.router.IDToURI("district.ByIDGet", int(*report.DistrictID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nhttp.NewError("district uri: %w", err)
|
return nil, nhttp.NewError("district uri: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
uri, err := res.router.IDStrToURI("publicreport.ByIDGet", report.PublicID)
|
uri, err := res.router.IDStrToURI("publicreport.ByIDGet", report.PublicID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nhttp.NewError("uri: %w", err)
|
return nil, nhttp.NewError("uri: %w", err)
|
||||||
}
|
}
|
||||||
location := types.Location{
|
report.District = &district_uri
|
||||||
Latitude: report.LocationLatitude.GetOr(0.0),
|
report.URI = uri
|
||||||
Longitude: report.LocationLongitude.GetOr(0.0),
|
return report, nil
|
||||||
}
|
|
||||||
return &publicreport{
|
|
||||||
District: district_uri,
|
|
||||||
ID: report.PublicID,
|
|
||||||
Address: report.AddressRaw,
|
|
||||||
Created: report.Created,
|
|
||||||
ImageCount: len(report.R.Images),
|
|
||||||
Location: location,
|
|
||||||
Status: report.Status.String(),
|
|
||||||
Type: report.ReportType.String(),
|
|
||||||
URI: uri,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,10 @@ const updateMarkers = () => {
|
||||||
markerData.location.longitude,
|
markerData.location.longitude,
|
||||||
markerData.location.latitude,
|
markerData.location.latitude,
|
||||||
]);
|
]);
|
||||||
marker.setDraggable(markerData.draggable ?? false);
|
marker.setDraggable(false);
|
||||||
} else {
|
} else {
|
||||||
marker = new maplibregl.Marker({
|
marker = new maplibregl.Marker({
|
||||||
draggable: markerData.draggable ?? false,
|
draggable: false,
|
||||||
})
|
})
|
||||||
.setLngLat([
|
.setLngLat([
|
||||||
markerData.location.longitude,
|
markerData.location.longitude,
|
||||||
|
|
@ -117,6 +117,7 @@ const updateMarkers = () => {
|
||||||
mapMarkers.value.set(markerData.id, marker);
|
mapMarkers.value.set(markerData.id, marker);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
frameMarkers();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Frame all markers in view
|
// Frame all markers in view
|
||||||
|
|
@ -130,7 +131,7 @@ const frameMarkers = () => {
|
||||||
lat: props.markers[0].location.latitude,
|
lat: props.markers[0].location.latitude,
|
||||||
lng: props.markers[0].location.longitude,
|
lng: props.markers[0].location.longitude,
|
||||||
},
|
},
|
||||||
{ duration: 1000 },
|
{ duration: 1000, zoom: 15 },
|
||||||
{ isInternalUpdate: true },
|
{ isInternalUpdate: true },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,6 @@
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
.map-container {
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
||||||
height: 500px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
#map {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.section-heading {
|
.section-heading {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -111,9 +98,15 @@ select.tall {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
/* Prevent touch scrolling issues */
|
/* Prevent touch scrolling issues */
|
||||||
touch-action: pan-y pinch-zoom;
|
touch-action: pan-y pinch-zoom;
|
||||||
}
|
}
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile-specific adjustments */
|
/* Mobile-specific adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
@ -131,11 +124,6 @@ select.tall {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#map {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,31 @@
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.map-container {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
height: 500px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* Prevent touch scrolling issues */
|
||||||
|
touch-action: pan-y pinch-zoom;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
.timeline {
|
.timeline {
|
||||||
border-left: 3px solid #dee2e6;
|
border-left: 3px solid #dee2e6;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item {
|
.timeline-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item:before {
|
.timeline-item:before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -20,17 +36,14 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #0d6efd;
|
background-color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-date {
|
.timeline-date {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<template>
|
<template>
|
||||||
|
<HeaderDistrict v-if="district" />
|
||||||
|
<Header v-else />
|
||||||
<div class="container my-4" v-if="report">
|
<div class="container my-4" v-if="report">
|
||||||
<!-- Report ID and Status Section -->
|
<!-- Report ID and Status Section -->
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
|
|
@ -62,7 +75,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<strong><i class="bi bi-pin-map me-2"></i>Location:</strong>
|
<strong><i class="bi bi-pin-map me-2"></i>Location:</strong>
|
||||||
<span>{{ report.address }}</span>
|
<span>{{ report.address.raw }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
@ -91,6 +104,29 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- History Timeline -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-clock-history me-2"></i>Request History
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="timeline">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in report.log"
|
||||||
|
:key="index"
|
||||||
|
class="timeline-item"
|
||||||
|
>
|
||||||
|
<div class="timeline-date">
|
||||||
|
{{ formatTimeRelative(item.created) }}
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-1">{{ item.type }}</h5>
|
||||||
|
<p class="mb-0">{{ item.message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container my-4" v-else>
|
<div class="container my-4" v-else>
|
||||||
<p>loading...</p>
|
<p>loading...</p>
|
||||||
|
|
@ -99,6 +135,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
|
import Header from "@/rmo/components/Header.vue";
|
||||||
|
import HeaderDistrict from "@/components/HeaderDistrict.vue";
|
||||||
import MapLocatorDisplay from "@/components/MapLocatorDisplay.vue";
|
import MapLocatorDisplay from "@/components/MapLocatorDisplay.vue";
|
||||||
import { useStoreDistrict } from "@/rmo/store/district";
|
import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import { useStorePublicreport } from "@/store/publicreport";
|
import { useStorePublicreport } from "@/store/publicreport";
|
||||||
|
|
@ -130,7 +168,7 @@ const markers = computed((): Marker[] => {
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: report.value.id,
|
id: props.id,
|
||||||
location: report.value.location,
|
location: report.value.location,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -32,25 +32,49 @@ export interface Geocode {
|
||||||
cell: number;
|
cell: number;
|
||||||
location: Location;
|
location: Location;
|
||||||
}
|
}
|
||||||
|
export interface LogEntryDTO {
|
||||||
|
created: string;
|
||||||
|
message: string;
|
||||||
|
type: string;
|
||||||
|
user_id: number;
|
||||||
|
}
|
||||||
|
export class LogEntry {
|
||||||
|
constructor(
|
||||||
|
public created: Date,
|
||||||
|
public message: string,
|
||||||
|
public type: string,
|
||||||
|
public user_id: number,
|
||||||
|
) {}
|
||||||
|
static fromJSON(json: LogEntryDTO): LogEntry {
|
||||||
|
return new LogEntry(
|
||||||
|
new Date(json.created),
|
||||||
|
json.message,
|
||||||
|
json.type,
|
||||||
|
json.user_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
export interface PublicreportDTO {
|
export interface PublicreportDTO {
|
||||||
address: string;
|
address: Address;
|
||||||
created: string;
|
created: string;
|
||||||
district: string;
|
district: string;
|
||||||
id: string;
|
id: string;
|
||||||
image_count: number;
|
image_count: number;
|
||||||
location: Location;
|
location: Location;
|
||||||
|
log: LogEntryDTO[];
|
||||||
status: string;
|
status: string;
|
||||||
type: string;
|
type: string;
|
||||||
uri: string;
|
uri: string;
|
||||||
}
|
}
|
||||||
export class Publicreport {
|
export class Publicreport {
|
||||||
constructor(
|
constructor(
|
||||||
public address: string,
|
public address: Address,
|
||||||
public created: Date,
|
public created: Date,
|
||||||
public district: string,
|
public district: string,
|
||||||
public id: string,
|
public id: string,
|
||||||
public image_count: number,
|
public image_count: number,
|
||||||
public location: Location,
|
public location: Location,
|
||||||
|
public log: LogEntry[],
|
||||||
public status: string,
|
public status: string,
|
||||||
public type: string,
|
public type: string,
|
||||||
public uri: string,
|
public uri: string,
|
||||||
|
|
@ -63,6 +87,7 @@ export class Publicreport {
|
||||||
json.id,
|
json.id,
|
||||||
json.image_count,
|
json.image_count,
|
||||||
json.location,
|
json.location,
|
||||||
|
json.log.map((l: LogEntryDTO) => LogEntry.fromJSON(l)),
|
||||||
json.status,
|
json.status,
|
||||||
json.type,
|
json.type,
|
||||||
json.uri,
|
json.uri,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue