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/event"
|
||||
"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/text"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
"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 PublicreportByID(ctx context.Context, report_id string) (*types.Report, error) {
|
||||
return publicreport.Report(ctx, report_id)
|
||||
}
|
||||
func PublicreportInvalid(ctx context.Context, user User, report_id string) error {
|
||||
report, err := reportFromID(ctx, user, report_id)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ WHERE i.id IN (1, 2, 3, 4)
|
|||
GROUP BY i.id;
|
||||
*/
|
||||
// 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(
|
||||
sm.Columns(
|
||||
"i.storage_uuid AS uuid",
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ import (
|
|||
)
|
||||
|
||||
func ReportsForOrganization(ctx context.Context, org_id int32) ([]*types.Report, error) {
|
||||
query := reportQuery(org_id)
|
||||
query := reportQuery()
|
||||
query.Apply(
|
||||
sm.Where(psql.Quote("publicreport", "report", "organization_id").EQ(psql.Arg(org_id))),
|
||||
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]())
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -34,7 +35,7 @@ func reportQueryToRows(ctx context.Context, org_id int32, query bob.BaseQuery[*d
|
|||
for i, row := range rows {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
func Reports(ctx context.Context, org_id int32, ids []int32) ([]*types.Report, error) {
|
||||
query := reportQuery(org_id)
|
||||
func Report(ctx context.Context, public_id string) (*types.Report, error) {
|
||||
query := reportQuery()
|
||||
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))),
|
||||
)
|
||||
return reportQueryToRows(ctx, org_id, query)
|
||||
return reportQueryToRows(ctx, query)
|
||||
}
|
||||
func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error) {
|
||||
type _Row struct {
|
||||
|
|
@ -92,10 +108,11 @@ func ReportsForOrganizationCount(ctx context.Context, org_id int32) (uint, error
|
|||
}
|
||||
return row.Count, nil
|
||||
}
|
||||
func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] {
|
||||
func reportQuery() bob.BaseQuery[*dialect.SelectQuery] {
|
||||
return psql.Select(
|
||||
sm.Columns(
|
||||
"address_country AS \"address.country\"",
|
||||
"address_gid AS \"address.gid\"",
|
||||
"address_locality AS \"address.locality\"",
|
||||
"address_number AS \"address.number\"",
|
||||
"address_postal_code AS \"address.postal_code\"",
|
||||
|
|
@ -106,6 +123,7 @@ func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] {
|
|||
"id",
|
||||
"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\"",
|
||||
"organization_id",
|
||||
"public_id",
|
||||
"report_type",
|
||||
"reporter_email AS \"reporter.email\"",
|
||||
|
|
@ -114,6 +132,5 @@ func reportQuery(org_id int32) bob.BaseQuery[*dialect.SelectQuery] {
|
|||
"status",
|
||||
),
|
||||
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"`
|
||||
Log []LogEntry `db:"-" json:"log"`
|
||||
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"`
|
||||
Reporter Contact `db:"reporter" json:"reporter"`
|
||||
Status string `db:"status" json:"status"`
|
||||
Type string `db:"report_type" json:"type"`
|
||||
URI string `db:"-" json:"uri"`
|
||||
Water *Water `db:"water" json:"water"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type district struct {
|
|||
Name string `json:"name"`
|
||||
PhoneOffice string `json:"phone_office"`
|
||||
Slug string `json:"slug"`
|
||||
URI string `json:"uri"`
|
||||
URLLogo string `json:"url_logo"`
|
||||
URLWebsite string `json:"url_website"`
|
||||
}
|
||||
|
|
@ -75,10 +76,15 @@ func newDistrict(r *router, org *platform.Organization) (*district, error) {
|
|||
if err != nil {
|
||||
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{
|
||||
Name: org.Name(),
|
||||
PhoneOffice: org.PhoneOffice(),
|
||||
Slug: slug,
|
||||
URI: uri,
|
||||
URLLogo: logo,
|
||||
URLWebsite: org.Website(),
|
||||
}, nil
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package resource
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
|
|
@ -16,25 +15,13 @@ type publicreportR struct {
|
|||
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 {
|
||||
return &publicreportR{
|
||||
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)
|
||||
public_id := vars["id"]
|
||||
if public_id == "" {
|
||||
|
|
@ -44,27 +31,18 @@ func (res *publicreportR) ByID(ctx context.Context, r *http.Request, query Query
|
|||
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)
|
||||
var district_uri string
|
||||
if report.DistrictID != nil {
|
||||
district_uri, err = res.router.IDToURI("district.ByIDGet", int(*report.DistrictID))
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("district uri: %w", err)
|
||||
}
|
||||
}
|
||||
uri, err := res.router.IDStrToURI("publicreport.ByIDGet", report.PublicID)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("uri: %w", err)
|
||||
}
|
||||
location := types.Location{
|
||||
Latitude: report.LocationLatitude.GetOr(0.0),
|
||||
Longitude: report.LocationLongitude.GetOr(0.0),
|
||||
}
|
||||
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
|
||||
report.District = &district_uri
|
||||
report.URI = uri
|
||||
return report, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,10 +103,10 @@ const updateMarkers = () => {
|
|||
markerData.location.longitude,
|
||||
markerData.location.latitude,
|
||||
]);
|
||||
marker.setDraggable(markerData.draggable ?? false);
|
||||
marker.setDraggable(false);
|
||||
} else {
|
||||
marker = new maplibregl.Marker({
|
||||
draggable: markerData.draggable ?? false,
|
||||
draggable: false,
|
||||
})
|
||||
.setLngLat([
|
||||
markerData.location.longitude,
|
||||
|
|
@ -117,6 +117,7 @@ const updateMarkers = () => {
|
|||
mapMarkers.value.set(markerData.id, marker);
|
||||
}
|
||||
});
|
||||
frameMarkers();
|
||||
};
|
||||
|
||||
// Frame all markers in view
|
||||
|
|
@ -130,7 +131,7 @@ const frameMarkers = () => {
|
|||
lat: props.markers[0].location.latitude,
|
||||
lng: props.markers[0].location.longitude,
|
||||
},
|
||||
{ duration: 1000 },
|
||||
{ duration: 1000, zoom: 15 },
|
||||
{ isInternalUpdate: true },
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -13,19 +13,6 @@
|
|||
margin-bottom: 1rem;
|
||||
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 {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
|
|
@ -111,9 +98,15 @@ select.tall {
|
|||
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%;
|
||||
}
|
||||
|
||||
/* Mobile-specific adjustments */
|
||||
@media (max-width: 768px) {
|
||||
|
|
@ -131,11 +124,6 @@ select.tall {
|
|||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,31 @@
|
|||
<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 {
|
||||
border-left: 3px solid #dee2e6;
|
||||
padding-left: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.timeline-item:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
|
@ -20,17 +36,14 @@
|
|||
border-radius: 50%;
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<HeaderDistrict v-if="district" />
|
||||
<Header v-else />
|
||||
<div class="container my-4" v-if="report">
|
||||
<!-- Report ID and Status Section -->
|
||||
<div class="card mb-4">
|
||||
|
|
@ -62,7 +75,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<strong><i class="bi bi-pin-map me-2"></i>Location:</strong>
|
||||
<span>{{ report.address }}</span>
|
||||
<span>{{ report.address.raw }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
|
@ -91,6 +104,29 @@
|
|||
</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 class="container my-4" v-else>
|
||||
<p>loading...</p>
|
||||
|
|
@ -99,6 +135,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
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 { useStoreDistrict } from "@/rmo/store/district";
|
||||
import { useStorePublicreport } from "@/store/publicreport";
|
||||
|
|
@ -130,7 +168,7 @@ const markers = computed((): Marker[] => {
|
|||
}
|
||||
return [
|
||||
{
|
||||
id: report.value.id,
|
||||
id: props.id,
|
||||
location: report.value.location,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -32,25 +32,49 @@ export interface Geocode {
|
|||
cell: number;
|
||||
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 {
|
||||
address: string;
|
||||
address: Address;
|
||||
created: string;
|
||||
district: string;
|
||||
id: string;
|
||||
image_count: number;
|
||||
location: Location;
|
||||
log: LogEntryDTO[];
|
||||
status: string;
|
||||
type: string;
|
||||
uri: string;
|
||||
}
|
||||
export class Publicreport {
|
||||
constructor(
|
||||
public address: string,
|
||||
public address: Address,
|
||||
public created: Date,
|
||||
public district: string,
|
||||
public id: string,
|
||||
public image_count: number,
|
||||
public location: Location,
|
||||
public log: LogEntry[],
|
||||
public status: string,
|
||||
public type: string,
|
||||
public uri: string,
|
||||
|
|
@ -63,6 +87,7 @@ export class Publicreport {
|
|||
json.id,
|
||||
json.image_count,
|
||||
json.location,
|
||||
json.log.map((l: LogEntryDTO) => LogEntry.fromJSON(l)),
|
||||
json.status,
|
||||
json.type,
|
||||
json.uri,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue