Fix update bug with work items and activities. Fix placement of item in AR view
This commit is contained in:
@@ -31,6 +31,7 @@ const shapes = {
|
|||||||
resources: [require("./models/clipboard.mtl")],
|
resources: [require("./models/clipboard.mtl")],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const distance = (vectorA, vectorB) => {
|
const distance = (vectorA, vectorB) => {
|
||||||
return Math.sqrt(
|
return Math.sqrt(
|
||||||
(vectorB[0] - vectorA[0]) * (vectorB[0] - vectorA[0]) +
|
(vectorB[0] - vectorA[0]) * (vectorB[0] - vectorA[0]) +
|
||||||
@@ -46,56 +47,55 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
scale: [0.15, 0.15, 0.15],
|
scale: [0.2, 0.2, 0.2],
|
||||||
shouldBillboard: true,
|
shouldBillboard: true,
|
||||||
|
trackingInitialized: false,
|
||||||
|
haveAnchor: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleLoadEnd() {
|
handleLoadEnd() {
|
||||||
this.arScene.getCameraOrientationAsync().then((orientation) => {
|
this.arScene.getCameraOrientationAsync().then((orientation) => {
|
||||||
|
console.log(orientation)
|
||||||
return this.arScene
|
return this.arScene
|
||||||
.performARHitTestWithRay(orientation.forward)
|
.performARHitTestWithRay(orientation.forward)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
// Default position is just 1.5 meters in front of the user.
|
|
||||||
const forward = orientation.forward
|
const forward = orientation.forward
|
||||||
const defaultPosition = [
|
const position = orientation.position
|
||||||
forward[0] * 1.5,
|
// Default position is just one meter in front of the user.
|
||||||
forward[1] * 1.5,
|
const defaultPosition =
|
||||||
forward[2] * 1.5,
|
[forward[0] * 1.0, forward[1] * 1.0, forward[2]] * 1.0
|
||||||
]
|
|
||||||
let hitResultPosition = null
|
let hitResultPosition = null
|
||||||
|
|
||||||
|
console.log(orientation)
|
||||||
|
console.log(results)
|
||||||
|
|
||||||
// Filter the hit test results based on the position.
|
// Filter the hit test results based on the position.
|
||||||
if (results.length > 0) {
|
for (var i = 0; i < results.length; i++) {
|
||||||
for (var i = 0; i < results.length; i++) {
|
let result = results[i]
|
||||||
let result = results[i]
|
|
||||||
|
|
||||||
if (result.type == "ExistingPlaneUsingExtent") {
|
if (result.type == "ExistingPlaneUsingExtent") {
|
||||||
let distance = Math.sqrt(
|
let distance = Math.sqrt(
|
||||||
(result.transform.position[0] - position[0]) *
|
(result.transform.position[0] - position[0]) *
|
||||||
(result.transform.position[0] - position[0]) +
|
(result.transform.position[0] - position[0]) +
|
||||||
(result.transform.position[1] - position[1]) *
|
(result.transform.position[1] - position[1]) *
|
||||||
(result.transform.position[1] - position[1]) +
|
(result.transform.position[1] - position[1]) +
|
||||||
(result.transform.position[2] - position[2]) *
|
(result.transform.position[2] - position[2]) *
|
||||||
(result.transform.position[2] - position[2])
|
(result.transform.position[2] - position[2])
|
||||||
)
|
)
|
||||||
if (distance > 0.2 && distance < 10) {
|
if (distance > 0.2 && distance < 10) {
|
||||||
// If we found a plane greater than .2 and less than 10 meters away then choose it!
|
// If we found a plane greater than .2 and less than 10 meters away then choose it!
|
||||||
hitResultPosition = result.transform.position
|
hitResultPosition = result.transform.position
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if (result.type == "FeaturePoint" && !hitResultPosition) {
|
} else if (result.type == "FeaturePoint" && !hitResultPosition) {
|
||||||
// If we haven't found a plane and this feature point is within range,
|
// If we haven't found a plane and this feature point is within range,
|
||||||
// then we'll use it as the initial display point
|
// then we'll use it as the initial display point
|
||||||
let distance = this._distance(
|
let d = distance(position, result.transform.position)
|
||||||
position,
|
|
||||||
result.transform.position
|
|
||||||
)
|
|
||||||
|
|
||||||
if (distance > 0.2 && distance < 10) {
|
if (d > 0.2 && d < 10) {
|
||||||
hitResultPosition = result.transform.position
|
hitResultPosition = result.transform.position
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,9 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
this.updateInitialRotation()
|
this.updateInitialRotation()
|
||||||
}, 200)
|
}, 200)
|
||||||
})
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +135,16 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleTrackingInitialized() {
|
||||||
|
this.setState({ trackingInitialized: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleAnchorFound() {
|
||||||
|
this.setState({ haveAnchor: true })
|
||||||
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleClick(position, source) {
|
handleClick(position, source) {
|
||||||
const { workItemId } = this.props
|
const { workItemId } = this.props
|
||||||
@@ -142,11 +155,22 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { position, scale, rotation, shouldBillboard } = this.state
|
const {
|
||||||
|
position,
|
||||||
|
scale,
|
||||||
|
rotation,
|
||||||
|
shouldBillboard,
|
||||||
|
trackingInitialized,
|
||||||
|
haveAnchor,
|
||||||
|
} = this.state
|
||||||
const shape = shapes[this.props.workItemType]
|
const shape = shapes[this.props.workItemType]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViroARScene ref={(ref) => (this.arScene = ref)}>
|
<ViroARScene
|
||||||
|
anchorDetectionTypes="PlanesHorizontal"
|
||||||
|
ref={(ref) => (this.arScene = ref)}
|
||||||
|
onTrackingInitialized={this.handleTrackingInitialized}
|
||||||
|
onAnchorFound={this.handleAnchorFound}>
|
||||||
<ViroAmbientLight color="#ffffff" intensity={200} />
|
<ViroAmbientLight color="#ffffff" intensity={200} />
|
||||||
<ViroNode
|
<ViroNode
|
||||||
ref={(ref) => (this.arNode = ref)}
|
ref={(ref) => (this.arNode = ref)}
|
||||||
@@ -165,16 +189,18 @@ class WorkItemSceneAR extends React.Component {
|
|||||||
shadowFarZ={6}
|
shadowFarZ={6}
|
||||||
shadowOpacity={0.9}
|
shadowOpacity={0.9}
|
||||||
/>
|
/>
|
||||||
{shape && (
|
{shape &&
|
||||||
<Viro3DObject
|
trackingInitialized &&
|
||||||
position={[0, 0, -1]}
|
haveAnchor && (
|
||||||
source={shape.source}
|
<Viro3DObject
|
||||||
resources={shape.resources}
|
position={[0, 0, -1]}
|
||||||
type="OBJ"
|
source={shape.source}
|
||||||
onLoadEnd={this.handleLoadEnd}
|
resources={shape.resources}
|
||||||
onClick={this.handleClick}
|
type="OBJ"
|
||||||
/>
|
onLoadEnd={this.handleLoadEnd}
|
||||||
)}
|
onClick={this.handleClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ViroSurface
|
<ViroSurface
|
||||||
rotation={[-90, 0, 0]}
|
rotation={[-90, 0, 0]}
|
||||||
position={[0, -0.001, 0]}
|
position={[0, -0.001, 0]}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export class Activity extends React.Component {
|
|||||||
},
|
},
|
||||||
photos: {
|
photos: {
|
||||||
isValid: (r, v) => v && v.length > 0,
|
isValid: (r, v) => v && v.length > 0,
|
||||||
initValue: [],
|
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
isValid: (r, v) => v !== "",
|
isValid: (r, v) => v !== "",
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export class Login extends React.Component {
|
|||||||
},
|
},
|
||||||
rememberMe: {
|
rememberMe: {
|
||||||
alwaysGet: true,
|
alwaysGet: true,
|
||||||
initValue: true,
|
|
||||||
isValid: true,
|
isValid: true,
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
@@ -76,7 +75,10 @@ export class Login extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
binder: new FormBinder({ email: config.defaultUser }, Login.bindings),
|
binder: new FormBinder(
|
||||||
|
{ email: config.defaultUser, rememberMe: true },
|
||||||
|
Login.bindings
|
||||||
|
),
|
||||||
messageModal: null,
|
messageModal: null,
|
||||||
apiModal: null,
|
apiModal: null,
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export class WorkItem extends React.Component {
|
|||||||
},
|
},
|
||||||
photos: {
|
photos: {
|
||||||
isValid: (r, v) => v && v.length > 0,
|
isValid: (r, v) => v && v.length > 0,
|
||||||
initValue: [],
|
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
isValid: (r, v) => v !== "",
|
isValid: (r, v) => v !== "",
|
||||||
@@ -84,7 +83,6 @@ export class WorkItem extends React.Component {
|
|||||||
binder: new FormBinder({}, WorkItem.bindings),
|
binder: new FormBinder({}, WorkItem.bindings),
|
||||||
messageModal: null,
|
messageModal: null,
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
region: config.initialRegion,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { search } = this.props.location
|
const { search } = this.props.location
|
||||||
@@ -104,10 +102,15 @@ export class WorkItem extends React.Component {
|
|||||||
longitudeDelta: 0.01,
|
longitudeDelta: 0.01,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.mapView) {
|
||||||
|
this.mapView.animateToRegion(region)
|
||||||
|
} else {
|
||||||
|
this.goToRegion = region
|
||||||
|
}
|
||||||
|
|
||||||
workItem.location = formatLatLng(lat, lng)
|
workItem.location = formatLatLng(lat, lng)
|
||||||
this.setState({
|
this.setState({
|
||||||
binder: new FormBinder(workItem, WorkItem.bindings),
|
binder: new FormBinder(workItem, WorkItem.bindings),
|
||||||
region,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -209,6 +212,14 @@ export class WorkItem extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleOnMapReady() {
|
||||||
|
if (this.goToRegion && this.mapView) {
|
||||||
|
this.mapView.animateToRegion(this.goToRegion)
|
||||||
|
this.goToRegion = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleStartAddressLookup(latLng) {
|
handleStartAddressLookup(latLng) {
|
||||||
api
|
api
|
||||||
@@ -217,7 +228,6 @@ export class WorkItem extends React.Component {
|
|||||||
if (this.addressInput) {
|
if (this.addressInput) {
|
||||||
this.addressInput.handleChangeText(address)
|
this.addressInput.handleChangeText(address)
|
||||||
}
|
}
|
||||||
this.setState({ region: this.mapView.region })
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (this.addressInput) {
|
if (this.addressInput) {
|
||||||
@@ -237,7 +247,7 @@ export class WorkItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { binder, messageModal, waitModal, region } = this.state
|
const { binder, messageModal, waitModal } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@@ -284,8 +294,9 @@ export class WorkItem extends React.Component {
|
|||||||
showsIndoors={false}
|
showsIndoors={false}
|
||||||
zoomControlEnabled={false}
|
zoomControlEnabled={false}
|
||||||
rotateEnabled={false}
|
rotateEnabled={false}
|
||||||
region={region}
|
initialRegion={config.initialRegion}
|
||||||
onRegionChange={this.handleRegionChange}
|
onRegionChange={this.handleRegionChange}
|
||||||
|
onMapReady={this.handleOnMapReady}
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
name="target"
|
name="target"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React from "react"
|
|||||||
import { Platform } from "react-native"
|
import { Platform } from "react-native"
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
//localIPAddr: "192.168.1.175",
|
localIPAddr: "192.168.1.175",
|
||||||
localIPAddr: "192.168.1.14",
|
//localIPAddr: "192.168.1.14",
|
||||||
viroAPIKey: "06F37B6A-74DA-4A83-965A-7DE2209A5C46",
|
viroAPIKey: "06F37B6A-74DA-4A83-965A-7DE2209A5C46",
|
||||||
googleGeocodeAPIKey: "AIzaSyCs4JVT6gysnY5dAJ7KjVJYeykLv_xz1GI",
|
googleGeocodeAPIKey: "AIzaSyCs4JVT6gysnY5dAJ7KjVJYeykLv_xz1GI",
|
||||||
googleGeocodeURL: "https://maps.googleapis.com/maps/api/geocode/json",
|
googleGeocodeURL: "https://maps.googleapis.com/maps/api/geocode/json",
|
||||||
|
|||||||
@@ -94,8 +94,7 @@ export class BoundPhotoPanel extends Component {
|
|||||||
handleLayout(e) {
|
handleLayout(e) {
|
||||||
if (!this.state.photoSize) {
|
if (!this.state.photoSize) {
|
||||||
const { layout } = e.nativeEvent
|
const { layout } = e.nativeEvent
|
||||||
const { screenWidth, screenHeight } = getScreenPortraitDimensions()
|
const ratio = 3 / 4
|
||||||
const ratio = screenWidth / screenHeight
|
|
||||||
const width = (layout.width - photoSpacing) / 2
|
const width = (layout.width - photoSpacing) / 2
|
||||||
const height = width / ratio
|
const height = width / ratio
|
||||||
|
|
||||||
@@ -106,9 +105,8 @@ export class BoundPhotoPanel extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { photoSize, value: assetIds } = this.state
|
const { photoSize, value: assetIds } = this.state
|
||||||
|
|
||||||
const renderPhoto = (index, size, paddingRight) => {
|
const renderPhoto = (index) => {
|
||||||
const assetId = assetIds[index]
|
const assetId = assetIds[index]
|
||||||
const { photoSize } = this.state
|
|
||||||
|
|
||||||
if (assetId) {
|
if (assetId) {
|
||||||
console.log(api.makeImageUrl(assetId))
|
console.log(api.makeImageUrl(assetId))
|
||||||
@@ -118,9 +116,8 @@ export class BoundPhotoPanel extends Component {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={assetId || "blank" + index.toString()}
|
key={assetId || "blank" + index.toString()}
|
||||||
style={{
|
style={{
|
||||||
width: size.width,
|
width: photoSize.width,
|
||||||
height: size.height,
|
height: photoSize.height,
|
||||||
paddingRight,
|
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: "gray",
|
borderColor: "gray",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
@@ -157,14 +154,14 @@ export class BoundPhotoPanel extends Component {
|
|||||||
</Text>
|
</Text>
|
||||||
{photoSize && (
|
{photoSize && (
|
||||||
<View style={styles.photoRow}>
|
<View style={styles.photoRow}>
|
||||||
{renderPhoto(0, photoSize, photoSpacing)}
|
{renderPhoto(0)}
|
||||||
{renderPhoto(1, photoSize)}
|
{renderPhoto(1)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{photoSize && (
|
{photoSize && (
|
||||||
<View style={styles.photoRow}>
|
<View style={styles.photoRow}>
|
||||||
{renderPhoto(2, photoSize, photoSpacing)}
|
{renderPhoto(2)}
|
||||||
{renderPhoto(3, photoSize)}
|
{renderPhoto(3)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
5
server/package-lock.json
generated
5
server/package-lock.json
generated
@@ -1596,6 +1596,11 @@
|
|||||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"deepmerge": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw=="
|
||||||
|
},
|
||||||
"default-require-extensions": {
|
"default-require-extensions": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"config": "^1.25.1",
|
"config": "^1.25.1",
|
||||||
"cors": "^2.8.3",
|
"cors": "^2.8.3",
|
||||||
"credential": "^2.0.0",
|
"credential": "^2.0.0",
|
||||||
|
"deepmerge": "^2.1.0",
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"gridfs-stream": "jlyonsmith/gridfs-stream",
|
"gridfs-stream": "jlyonsmith/gridfs-stream",
|
||||||
"handlebars": "^4.0.10",
|
"handlebars": "^4.0.10",
|
||||||
|
|||||||
@@ -111,13 +111,7 @@ export class ActivityRoutes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Activity = this.db.Activity
|
let Activity = this.db.Activity
|
||||||
let activityUpdates = null
|
let activityUpdates = req.body
|
||||||
|
|
||||||
try {
|
|
||||||
activityUpdates = new Activity(req.body)
|
|
||||||
} catch (err) {
|
|
||||||
throw createError.BadRequest("Invalid data")
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundActivity = await Activity.findById(activityUpdates._id)
|
const foundActivity = await Activity.findById(activityUpdates._id)
|
||||||
|
|
||||||
@@ -127,9 +121,13 @@ export class ActivityRoutes {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
foundActivity.merge(activityUpdates)
|
// Strip off all BSON types
|
||||||
|
foundActivity = JSON.parse(JSON.stringify(foundActivity))
|
||||||
|
|
||||||
const savedActivity = await foundActivity.save()
|
const mergedActivity = Activity.hydrate(
|
||||||
|
merge(foundActivity, activityUpdates)
|
||||||
|
)
|
||||||
|
const savedActivity = await mergedActivity.save()
|
||||||
|
|
||||||
res.json(savedActivity.toClient())
|
res.json(savedActivity.toClient())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import passport from "passport"
|
import passport from "passport"
|
||||||
import createError from "http-errors"
|
import createError from "http-errors"
|
||||||
import autobind from "autobind-decorator"
|
import autobind from "autobind-decorator"
|
||||||
|
import merge from "deepmerge"
|
||||||
import { catchAll } from "."
|
import { catchAll } from "."
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -146,15 +147,8 @@ export class WorkItemRoutes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let WorkItem = this.db.WorkItem
|
let WorkItem = this.db.WorkItem
|
||||||
let workItemUpdates = null
|
let workItemUpdates = req.body
|
||||||
|
let foundWorkItem = await WorkItem.findById(workItemUpdates._id)
|
||||||
try {
|
|
||||||
workItemUpdates = new WorkItem(req.body)
|
|
||||||
} catch (err) {
|
|
||||||
throw createError.BadRequest("Invalid data")
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundWorkItem = await WorkItem.findById(workItemUpdates._id)
|
|
||||||
|
|
||||||
if (!foundWorkItem) {
|
if (!foundWorkItem) {
|
||||||
return next(
|
return next(
|
||||||
@@ -162,9 +156,13 @@ export class WorkItemRoutes {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
foundWorkItem.merge(workItemUpdates)
|
// Strip off all BSON types
|
||||||
|
foundWorkItem = JSON.parse(JSON.stringify(foundWorkItem))
|
||||||
|
|
||||||
const savedWorkItem = await foundWorkItem.save()
|
const mergedWorkItem = WorkItem.hydrate(
|
||||||
|
merge(foundWorkItem, workItemUpdates)
|
||||||
|
)
|
||||||
|
const savedWorkItem = await mergedWorkItem.save()
|
||||||
|
|
||||||
res.json(savedWorkItem.toClient())
|
res.json(savedWorkItem.toClient())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const workItemSchema = new Schema(
|
|||||||
coordinates: [Number],
|
coordinates: [Number],
|
||||||
},
|
},
|
||||||
address: String,
|
address: String,
|
||||||
photos: [Schema.Types.ObjectId],
|
photos: [{ type: Schema.Types.ObjectId }],
|
||||||
details: String,
|
details: String,
|
||||||
ticketNumber: { type: Number, default: 0 },
|
ticketNumber: { type: Number, default: 0 },
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user