updating frontend to new standards
This commit is contained in:
@@ -490,3 +490,90 @@
|
||||
{"level":"debug","time":"2023-01-09T22:18:51-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"15420", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"15420", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"level":"info","time":"2023-01-10T21:30:33-05:00","message":"Configuration loaded successfully..."}
|
||||
{"level":"debug","time":"2023-01-10T21:30:33-05:00","message":"{Timezone:America/New_York Server:{Port:3500 LocationFilesDir:./app/files/} Logger:{Level:debug LoggingFile:./app/log/goInventorize.log} Authentication:{BasicAuth:false UserName:admin Password:password} Development:false}"}
|
||||
{"level":"info","time":"2023-01-10T21:30:33-05:00","message":"Database and Config loaded, starting webserver..."}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"level":"info","time":"2023-01-10T21:31:52-05:00","message":"Getting all Rooms"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/rooms"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"level":"debug","time":"2023-01-10T22:30:28-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"level":"debug","time":"2023-01-10T22:30:32-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"level":"info","time":"2023-01-10T22:30:37-05:00","message":"Getting all Rooms"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/rooms"}
|
||||
{"level":"debug","time":"2023-01-10T22:30:38-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"level":"debug","time":"2023-01-10T22:32:19-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"level":"info","time":"2023-01-10T22:32:20-05:00","message":"Getting all Rooms"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/rooms"}
|
||||
{"level":"info","time":"2023-01-10T22:32:23-05:00","message":"Getting all Rooms"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/rooms"}
|
||||
{"level":"debug","time":"2023-01-10T22:32:24-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"level":"debug","time":"2023-01-10T22:32:28-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/config"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/overview/all"}
|
||||
{"level":"debug","time":"2023-01-10T22:38:41-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"level":"debug","time":"2023-01-10T22:40:26-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"level":"debug","time":"2023-01-10T22:41:33-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"404", "method":"GET", "path":"/files/locations/undefined/undefined"}
|
||||
{"level":"debug","time":"2023-01-10T22:42:00-05:00","message":"Returning Locations: [{ID:1 Name:Location0 Description:This is my test description0 Notes:Notes for my location!0 Address: SquareFeet:2500 Latitude:120N Longitude:120N DatePurchased: PurchasePrice:125000 CurrentValue: CoverPhoto:Location0_cover.png Photos:[] Files:[] Rooms:[]}]"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"200", "method":"GET", "path":"/api/locations"}
|
||||
{"logtype":"webserver", "pid":"14996", "requestid":"", "status":"404", "method":"GET", "path":"/files/locations/undefined/undefined"}
|
||||
|
@@ -1,23 +1,24 @@
|
||||
import React, {useState, useEffect } from 'react';
|
||||
import { useAtom } from 'jotai';
|
||||
import HomePage from './components/pages/HomePage';
|
||||
import LocationsPage from './components/pages/LocationsPage';
|
||||
import LocationsPage from './components/pages/Locations/LocationsPage';
|
||||
import RoomsPage from './components/pages/RoomsPage';
|
||||
import LocationForm from './components/forms/LocationForm'
|
||||
import LocationForm from './components/pages/Locations/NewLocationForm'
|
||||
import NotFound from './components/pages/NotFound';
|
||||
import { BrowserRouter as Router, Routes, Route} from 'react-router-dom'
|
||||
import { Modal, Button, Text, Group, TextInput, Loader, AppShell, MediaQuery } from '@mantine/core';
|
||||
import { useDebouncedValue, useLocalStorageValue } from '@mantine/hooks';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { Loader, AppShell, MediaQuery, Header, Center, Title, Burger, useMantineTheme } from '@mantine/core';
|
||||
// import { useDebouncedValue, useLocalStorageValue } from '@mantine/hooks';
|
||||
|
||||
import { backendAPI, defaultURLS } from './services/backend-api';
|
||||
|
||||
import { serverConfigAtom } from './state/state'
|
||||
import { serverConfigAtom, notificationQueueAtom } from './state/state'
|
||||
|
||||
import { generateNotification } from './components/Utilities';
|
||||
|
||||
|
||||
import SideBar from './components/SideBar';
|
||||
import AppHeader from './components/AppHeader';
|
||||
import LocationDetailsPage from './components/pages/LocationDetailsPage';
|
||||
// import AppHeader from './components/AppHeader';
|
||||
import LocationDetailsPage from './components/pages/Locations/LocationDetailsPage';
|
||||
|
||||
|
||||
|
||||
@@ -25,52 +26,73 @@ function App() {
|
||||
// Main nav/sidebar appshell openend
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
const [, setServerConfig] = useAtom(serverConfigAtom)
|
||||
const [, setNotificationQueue] = useAtom(notificationQueueAtom)
|
||||
|
||||
const theme = useMantineTheme();
|
||||
|
||||
|
||||
// const navigate = useNavigate();
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// navigate("/")// Reset to homepage on new load anywhere in the app
|
||||
setIsLoading(true)
|
||||
async function fetchSettings() {
|
||||
backendAPI.get('/config').then(results => {
|
||||
|
||||
const fetchSettings = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const results = await backendAPI.get('/config')
|
||||
// console.log("FULL RESULTS: ", results.config.baseURL)
|
||||
results.data.baseURL = defaultURLS.baseURL
|
||||
console.log("CONFIG: ", results.data)
|
||||
setServerConfig(results.data)
|
||||
showNotification({
|
||||
title: 'Backend Notice',
|
||||
message: 'Config fetched from backend!',
|
||||
color: "green"
|
||||
})
|
||||
setIsLoading(false)
|
||||
}).catch(err => {
|
||||
showNotification({
|
||||
title: 'Backend Notice',
|
||||
message: `Failed to connect to backend! ${err}`,
|
||||
autoClose: false,
|
||||
color: "red",
|
||||
})
|
||||
setIsLoading(false)
|
||||
})
|
||||
|
||||
const notification = generateNotification('success', 'Config Fetched', 'Config fetched from backend')
|
||||
setNotificationQueue((currentQueue) => ([...currentQueue, notification]))
|
||||
} catch(err) {
|
||||
const notification = generateNotification('error', 'Failed to fetch config', `Failed to fetch config: ${err}`)
|
||||
setNotificationQueue((currentQueue) => ([...currentQueue, notification]))
|
||||
}
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
fetchSettings();
|
||||
|
||||
|
||||
}, [])
|
||||
}, [setServerConfig, setNotificationQueue])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? <Center><Loader variant="bars" /></Center> :
|
||||
<Router>
|
||||
<AppShell
|
||||
navbarOffsetBreakpoint="sm" // navbarOffsetBreakpoint controls when navbar should no longer be offset with padding-left
|
||||
fixed // fixed prop on AppShell will be automatically added to Header and Navbar
|
||||
navbar={<SideBar />}
|
||||
header={<AppHeader />}
|
||||
header={
|
||||
<Header height={70} p="md">
|
||||
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
|
||||
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
|
||||
<Burger
|
||||
opened={opened}
|
||||
onClick={() => setOpened((o) => !o)}
|
||||
size="sm"
|
||||
color={theme.colors.gray[6]}
|
||||
mr="xl"
|
||||
/>
|
||||
</MediaQuery>
|
||||
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
|
||||
<Title order={4}>goInventorize</Title>
|
||||
</MediaQuery>
|
||||
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
|
||||
<Title>goInventorize</Title>
|
||||
</MediaQuery>
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
}
|
||||
>
|
||||
<Routes>
|
||||
<Route path='*' element={<NotFound />} />
|
||||
@@ -86,6 +108,8 @@ function App() {
|
||||
</Routes>
|
||||
</AppShell>
|
||||
</Router>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,11 +0,0 @@
|
||||
|
||||
|
||||
function Notifications() {
|
||||
|
||||
|
||||
return(
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default Notifications;
|
@@ -1,38 +0,0 @@
|
||||
import React, {useState, useEffect, useContext } from 'react';
|
||||
import { useAtom } from 'jotai';
|
||||
import { Header, MediaQuery, Burger, Text, ThemeIcon, Group, Title, createStyles, useMantineTheme } from '@mantine/core';
|
||||
import { BsHouseDoor } from 'react-icons/bs'
|
||||
import { menuOpenedAtom } from '../state/state';
|
||||
|
||||
|
||||
function AppHeader(props) {
|
||||
const theme = useMantineTheme()
|
||||
const [menuOpened, setMenuOpened] = useAtom(menuOpenedAtom)
|
||||
|
||||
|
||||
return (
|
||||
<Header height={70} padding="md">
|
||||
{/* You can handle other responsive styles with MediaQuery component or createStyles function */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
|
||||
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
|
||||
<Burger
|
||||
opened={menuOpened}
|
||||
onClick={() => setMenuOpened((o) => !o)}
|
||||
size="sm"
|
||||
color={theme.colors.gray[6]}
|
||||
mr="xl"
|
||||
/>
|
||||
</MediaQuery>
|
||||
<Group>
|
||||
<BsHouseDoor size={30} />
|
||||
<Title><Text inherit variant="gradient" gradient={{ from: 'indigo', to: 'cyan', deg: 45 }}>goInventorize</Text></Title>
|
||||
</Group>
|
||||
|
||||
</div>
|
||||
</Header>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default AppHeader
|
32
frontend/src/components/Notifications.js
Normal file
32
frontend/src/components/Notifications.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { useResetAtom } from 'jotai/utils'
|
||||
import { notificationQueueAtom, notificationHistoryAtom } from '../state/state.js';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function Notifications() {
|
||||
const [notificationQueue] = useAtom(notificationQueueAtom)
|
||||
const [,setNotificationHistory] = useAtom(notificationHistoryAtom)
|
||||
const resetNotifications = useResetAtom(notificationQueueAtom)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (notificationQueue.length > 0) {
|
||||
notificationQueue.forEach(notification => {
|
||||
// setting the full history so user can view it later
|
||||
setNotificationHistory((history) => [...history, notification])
|
||||
showNotification({
|
||||
title: notification.title,
|
||||
message: notification.message,
|
||||
color: notification.color,
|
||||
icon: notification.icon,
|
||||
disallowClose: notification.disallowClose,
|
||||
})
|
||||
});
|
||||
resetNotifications()
|
||||
}
|
||||
}, [notificationQueue, resetNotifications, setNotificationHistory])
|
||||
|
||||
}
|
||||
|
||||
export default Notifications
|
120
frontend/src/components/PhotoDisplay.js
Normal file
120
frontend/src/components/PhotoDisplay.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import {Image, Text, Modal, SimpleGrid, Title, Loader, Center, Card, Group, Badge, Button, Tooltip} from '@mantine/core'
|
||||
import { ExternalLink } from 'tabler-icons-react'
|
||||
import {useEffect, useState } from 'react'
|
||||
// import BackendAPI from '../services/backend'
|
||||
|
||||
const urlPrepend = "https://apps.ts.fedex.com/tsrphotos/"
|
||||
|
||||
function PhotoDisplay(props) {
|
||||
const { photos } = props
|
||||
//const [photosDisplay, setPhotosDisplay] = useState([])
|
||||
const [photosLoading, setPhotosLoading] = useState(false)
|
||||
const [numPhotos, setNumPhotos] = useState(photos.length)
|
||||
// Modal states
|
||||
const [photoOpened, setPhotoOpened] = useState(false)
|
||||
const [photoDetails, setPhotoDetails] = useState({
|
||||
"name": "",
|
||||
"data": null,
|
||||
"locationName": "",
|
||||
})
|
||||
const [rawPhotoArray, setRawPhotoArray] = useState([])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const createPhotos = async () => {
|
||||
setPhotosLoading(true)
|
||||
if (photos.length > 0) {
|
||||
photos.forEach(async (photo) => {
|
||||
console.log("Loading photo: ", photo.photo_path_txt)
|
||||
const fullUrl = `${urlPrepend}${photo.photo_path_txt}`
|
||||
let newPhoto =
|
||||
<Card key={photo.photo_path_txt} shadow="sm" p="lg" radius="md" withBorder>
|
||||
<Card.Section>
|
||||
<Image radius="md"
|
||||
key={photo.photo_path_txt}
|
||||
style={{ cursor: 'pointer'}}
|
||||
src={`${urlPrepend}${photo.photo_path_txt}`}
|
||||
onClick={() => displayPhoto(fullUrl, photo)}
|
||||
/>
|
||||
</Card.Section>
|
||||
<Group position="apart" mt="md" mb="xs">
|
||||
<Text weight={500}>{photo.photo_nm}</Text>
|
||||
<Badge color="pink" variant="light">
|
||||
{photo.network_location ? photo.network_location.location_nm : "No Location"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text size="sm" mb="lg" color="dimmed">
|
||||
{photo?.photo_notes_txt ? photo.photo_notes_txt : "No Photo Notes!"}
|
||||
</Text>
|
||||
{photo.latitude_nbr !== 0 && photo.longitude_nbr !== 0 &&
|
||||
<Tooltip label={`${photo.latitude_nbr}, ${photo.longitude_nbr}`}>
|
||||
<Button mb="xl" color="dark" component='a' href={`https://maps.google.com/?q=${photo.latitude_nbr},${photo.longitude_nbr}`} target="_blank" leftIcon={<ExternalLink />}>View Photo in Google Maps</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
</Card>
|
||||
setRawPhotoArray(currArray => [...currArray, newPhoto])
|
||||
// }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (photos.length > 0) {
|
||||
console.log("RECEIVED PHOTOS!", photos)
|
||||
setNumPhotos(photos.length)
|
||||
createPhotos()
|
||||
}
|
||||
}, [photos])
|
||||
|
||||
useEffect(() => {
|
||||
if (photos.length === rawPhotoArray.length) {
|
||||
console.log("Done loading! ")
|
||||
setPhotosLoading(false)
|
||||
}
|
||||
}, [rawPhotoArray, photos.length])
|
||||
|
||||
|
||||
|
||||
|
||||
// For displaying photos full screen
|
||||
const displayPhoto = async (photoUrl, photo) => {
|
||||
|
||||
setPhotoDetails({
|
||||
// data: 'data:image/jpeg;base64,' + photoBinary,
|
||||
data: photoUrl,
|
||||
name: photo.photo_nm,
|
||||
locationName: photo.network_location ? photo.network_location.location_nm : ""
|
||||
})
|
||||
setPhotoOpened(true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal opened={photoOpened} onClose={() => setPhotoOpened(false)} title={photoDetails.name} size="full">
|
||||
<Image radius="md"
|
||||
src={photoDetails.data}
|
||||
caption={photoDetails.locationName ? <><Text weight={700}>{photoDetails.locationName}</Text><Text>{photoDetails.name}</Text></>: <Text>{photoDetails.name}</Text>}
|
||||
withPlaceholder
|
||||
/>
|
||||
</Modal>
|
||||
<Title mb="lg" order={3}>Photos: {numPhotos}</Title>
|
||||
<SimpleGrid
|
||||
cols={4}
|
||||
breakpoints={[
|
||||
{ maxWidth: 1080, cols: 2, spacing: 'md' },
|
||||
// { maxWidth: 755, cols: 2, spacing: 'sm' },
|
||||
{ maxWidth: 600, cols: 1, spacing: 'sm' },
|
||||
]}
|
||||
>
|
||||
{rawPhotoArray}
|
||||
</SimpleGrid>
|
||||
{photosLoading && <Center><Loader variant='bars' size="sm" /></Center>}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PhotoDisplay
|
@@ -13,8 +13,8 @@ const sideBarData = [
|
||||
links: [
|
||||
{ label: 'View Locations', link: '/locations' },
|
||||
{ label: 'Add New Location', link: '/locations/new' },
|
||||
{ label: 'Outlook', link: '/' },
|
||||
{ label: 'Real time', link: '/' },
|
||||
// { label: 'Outlook', link: '/' },
|
||||
// { label: 'Real time', link: '/' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -24,11 +24,11 @@ const sideBarData = [
|
||||
links: [
|
||||
{ label: 'View Rooms', link: '/rooms' },
|
||||
{ label: 'Add New Room', link: '/rooms/new' },
|
||||
{ label: 'Releases schedule', link: '/'},
|
||||
// { label: 'Releases schedule', link: '/'},
|
||||
],
|
||||
},
|
||||
{ label: 'Cabinets', icon: BiCabinet, link: "/" },
|
||||
{ label: 'Items', icon: BsDiagram2, link: "/" },
|
||||
// { label: 'Cabinets', icon: BiCabinet, link: "/" },
|
||||
// { label: 'Items', icon: BsDiagram2, link: "/" },
|
||||
{ label: 'Settings', icon: GoGear, link: '/settings' },
|
||||
];
|
||||
|
||||
|
33
frontend/src/components/Utilities.js
Normal file
33
frontend/src/components/Utilities.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IconAlertTriangle, IconAlertCircle, IconCheck } from '@tabler/icons'
|
||||
|
||||
|
||||
export const generateNotification = (notificationType, title, message, noClose, id) => {
|
||||
// Generates the notification so you can push it to the queue
|
||||
let baseNotification = {
|
||||
id: id ? id : null,
|
||||
dissallowClose: noClose ? noClose : false,
|
||||
title: title,
|
||||
message: message,
|
||||
color: 'red',
|
||||
icon: null,
|
||||
}
|
||||
|
||||
switch (notificationType) {
|
||||
case "error":
|
||||
baseNotification.color = 'red'
|
||||
baseNotification.icon = <IconAlertTriangle size={16} />
|
||||
break;
|
||||
case "warning":
|
||||
baseNotification.color = 'yellow'
|
||||
baseNotification.icon = <IconAlertCircle size={16} />
|
||||
break;
|
||||
case "success":
|
||||
baseNotification.color = 'green'
|
||||
baseNotification.icon = <IconCheck size={16}/>
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
console.log("MY NOTIFICATION: ", baseNotification)
|
||||
return baseNotification
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Text, Button, Card, Group, Menu, Image, Badge } from '@mantine/core'
|
||||
import { useAtom } from 'jotai';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { activePageAtom, roomFilterAtom, serverConfigAtom } from '../../state/state';
|
||||
// import { useNavigate } from "react-router-dom";
|
||||
import { serverConfigAtom } from '../../state/state';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ import { Link } from 'react-router-dom';
|
||||
function LocationCard(props) {
|
||||
const {location, idx} = props
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
const [, setRoomsFilter] = useAtom(roomFilterAtom)
|
||||
const [, setActivePage] = useAtom(activePageAtom)
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const setRoomNumber = (rooms) => {
|
||||
if (rooms === null) {
|
||||
@@ -25,11 +21,6 @@ function LocationCard(props) {
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToRooms = (locationID, locationName) => {
|
||||
setActivePage("rooms")
|
||||
setRoomsFilter({"filterType": "location", "locationID": locationID, "commonName": "location", "metadata": locationName})
|
||||
navigate("/rooms")
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
@@ -51,9 +42,8 @@ function LocationCard(props) {
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text size="sm">{location.Description}</Text>
|
||||
<Group>
|
||||
<Button component={Link} to={`/locations/${location.ID}`} state={{ location: location }}>View Details</Button>
|
||||
<Button onClick={() => navigateToRooms(location.ID, location.Name)}>View Rooms</Button>
|
||||
<Group position='right' mt="xl">
|
||||
<Button component={Link} to={`/locations/${location.ID}`} state={{ location: location }}>View Location Details</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
|
@@ -1,148 +1,170 @@
|
||||
import React, {useState, useEffect } from 'react';
|
||||
import { useAtom } from 'jotai'
|
||||
import { serverConfigAtom } from '../../state/state';
|
||||
import { Loader, Center, Title, Group, Button, Space, Container, ThemeIcon, Text, Image } from '@mantine/core'
|
||||
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { GoLocation } from 'react-icons/go'
|
||||
|
||||
import { backendAPI } from '../../services/backend-api';
|
||||
|
||||
function LocationDetailsPage() {
|
||||
const location = useLocation()
|
||||
|
||||
|
||||
|
||||
// const [opened, setOpened] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
const [locationDetails, setLocationDetails] = useState({})
|
||||
|
||||
let navigate = useNavigate();
|
||||
|
||||
const notifications = useNotifications();
|
||||
|
||||
let routeLocationID = useParams()
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let locationDetailsProps = {}
|
||||
if (location.state) {
|
||||
if (location.state.hasOwnProperty('location')) {
|
||||
locationDetailsProps = location.state.location
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(locationDetailsProps).length === 0) {
|
||||
console.log("manually fetching location details!")
|
||||
setIsLoading(true)
|
||||
async function fetchSettings() {
|
||||
backendAPI.get(`/locations/${routeLocationID.id}`).then(results => {
|
||||
console.log("CONFIG IN LOCATIONS: ", serverConfig)
|
||||
console.log("LOCATIONS: ", results.data)
|
||||
setLocationDetails(results.data)
|
||||
setIsLoading(false)
|
||||
}).catch(err => {
|
||||
notifications.showNotification({
|
||||
title: 'Backend Notice',
|
||||
message: `Failed to fetch locations from backend! ${err}`,
|
||||
autoClose: false,
|
||||
color: "red",
|
||||
})
|
||||
setIsLoading(false)
|
||||
})
|
||||
|
||||
}
|
||||
fetchSettings();
|
||||
} else {
|
||||
console.log("Using passed state!", locationDetailsProps)
|
||||
setLocationDetails(locationDetailsProps)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center>{ isLoading && <Loader size="xl" variant="bars" />}</Center>
|
||||
<Center><Title order={1}>{locationDetails.Name}</Title></Center>
|
||||
<Button>Edit Location</Button>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Description: </Text>
|
||||
<Text>{locationDetails.Description}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Notes: </Text>
|
||||
<Text>{locationDetails.Notes}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Address: </Text>
|
||||
<Text>{locationDetails.Address}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Square Feet: </Text>
|
||||
<Text>{locationDetails.SquareFeet}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Lat/Long: </Text>
|
||||
<Text>{locationDetails.Latitude}/{locationDetails.Longitude}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Date Purchased: </Text>
|
||||
<Text>{locationDetails.DatePurchased}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Purchase Price: </Text>
|
||||
<Text>{locationDetails.PurchasePrice}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Current Value: </Text>
|
||||
<Text>{locationDetails.CurrentValue}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Image src={`${serverConfig.baseURL}/files/locations/${locationDetails.Name}/${locationDetails.CoverPhoto}`} />
|
||||
</Container>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
import React, {useState, useEffect } from 'react';
|
||||
import { useAtom } from 'jotai'
|
||||
import { serverConfigAtom } from '../../../state/state';
|
||||
import { BsDoorClosed } from 'react-icons/bs';
|
||||
import { BiEdit } from 'react-icons/bi';
|
||||
import { Loader, Center, Title, Group, Button, Space, Container, ThemeIcon, Text, Image, Accordion, Avatar } from '@mantine/core'
|
||||
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
import { GoLocation } from 'react-icons/go'
|
||||
|
||||
import { backendAPI } from '../../../services/backend-api';
|
||||
|
||||
function LocationDetailsPage() {
|
||||
const location = useLocation()
|
||||
|
||||
|
||||
|
||||
// const [opened, setOpened] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
const [locationDetails, setLocationDetails] = useState({})
|
||||
|
||||
let navigate = useNavigate();
|
||||
|
||||
const notifications = useNotifications();
|
||||
|
||||
let routeLocationID = useParams()
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let locationDetailsProps = {}
|
||||
if (location.state) {
|
||||
if (location.state.hasOwnProperty('location')) {
|
||||
locationDetailsProps = location.state.location
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(locationDetailsProps).length === 0) {
|
||||
console.log("manually fetching location details!")
|
||||
setIsLoading(true)
|
||||
async function fetchSettings() {
|
||||
backendAPI.get(`/locations/${routeLocationID.id}`).then(results => {
|
||||
console.log("CONFIG IN LOCATIONS: ", serverConfig)
|
||||
console.log("LOCATIONS: ", results.data)
|
||||
setLocationDetails(results.data)
|
||||
setIsLoading(false)
|
||||
}).catch(err => {
|
||||
notifications.showNotification({
|
||||
title: 'Backend Notice',
|
||||
message: `Failed to fetch locations from backend! ${err}`,
|
||||
autoClose: false,
|
||||
color: "red",
|
||||
})
|
||||
setIsLoading(false)
|
||||
})
|
||||
|
||||
}
|
||||
fetchSettings();
|
||||
} else {
|
||||
console.log("Using passed state!", locationDetailsProps)
|
||||
setLocationDetails(locationDetailsProps)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center>{ isLoading && <Loader size="xl" variant="bars" />}</Center>
|
||||
<Center><Title order={1}>{locationDetails.Name}</Title></Center>
|
||||
<Button color="yellow" leftIcon={<BiEdit size={24} />}>Edit Location</Button>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Description: </Text>
|
||||
<Text>{locationDetails.Description}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Notes: </Text>
|
||||
<Text>{locationDetails.Notes}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Address: </Text>
|
||||
<Text>{locationDetails.Address}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Square Feet: </Text>
|
||||
<Text>{locationDetails.SquareFeet}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Lat/Long: </Text>
|
||||
<Text>{locationDetails.Latitude}/{locationDetails.Longitude}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Date Purchased: </Text>
|
||||
<Text>{locationDetails.DatePurchased}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Purchase Price: </Text>
|
||||
<Text>{locationDetails.PurchasePrice}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Group>
|
||||
<ThemeIcon variant="light" size={30}><GoLocation /></ThemeIcon>
|
||||
<Text>Current Value: </Text>
|
||||
<Text>{locationDetails.CurrentValue}</Text>
|
||||
</Group>
|
||||
</Container>
|
||||
<Space h="md" />
|
||||
<Container>
|
||||
<Image src={`${serverConfig.baseURL}/files/locations/${locationDetails.Name}/${locationDetails.CoverPhoto}`} />
|
||||
</Container>
|
||||
<Accordion mt="xl">
|
||||
<Accordion.Item value="notifications">
|
||||
<Accordion.Control>
|
||||
<Group noWrap>
|
||||
<Avatar color="blue" radius="xl" size="lg">
|
||||
<BsDoorClosed />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text>Rooms</Text>
|
||||
<Text size="sm" color="dimmed" weight={400}>
|
||||
Rooms in this Location
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default LocationDetailsPage;
|
@@ -1,76 +1,76 @@
|
||||
import React, {useState, useEffect } from 'react';
|
||||
import { useAtom } from 'jotai'
|
||||
import { serverConfigAtom } from '../../state/state';
|
||||
import { HiPlus } from 'react-icons/hi'
|
||||
import { Loader, Center, SimpleGrid, Title, Group, Button } from '@mantine/core'
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
|
||||
|
||||
import { backendAPI } from '../../services/backend-api';
|
||||
import LocationCard from '../cards/LocationCard';
|
||||
|
||||
|
||||
function LocationsPage() {
|
||||
// const [opened, setOpened] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [locations, setLocations] = useState([])
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
|
||||
let navigate = useNavigate();
|
||||
|
||||
|
||||
|
||||
const notifications = useNotifications();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log("LOADING LOCATIONS PAGE!")
|
||||
setIsLoading(true)
|
||||
async function fetchSettings() {
|
||||
backendAPI.get('/locations').then(results => {
|
||||
console.log("CONFIG IN LOCATIONS: ", serverConfig)
|
||||
console.log("LOCATIONS: ", results.data)
|
||||
setLocations(results.data)
|
||||
setIsLoading(false)
|
||||
}).catch(err => {
|
||||
notifications.showNotification({
|
||||
title: 'Backend Notice',
|
||||
message: `Failed to fetch locations from backend! ${err}`,
|
||||
autoClose: false,
|
||||
color: "red",
|
||||
})
|
||||
setIsLoading(false)
|
||||
})
|
||||
|
||||
}
|
||||
fetchSettings();
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center>{ isLoading && <Loader size="xl" variant="bars" />}</Center>
|
||||
<Center><Title order={1}>Locations</Title></Center>
|
||||
<Button mb="lg" leftIcon={<HiPlus />} onClick={(e) => {navigate("/locations/new")}}>Add New Location</Button>
|
||||
<SimpleGrid
|
||||
spacing="md"
|
||||
cols={4}
|
||||
breakpoints={[
|
||||
{ maxWidth: 'sm', cols: 1, spacing: 'md'},
|
||||
{ maxWidth: 'md', cols: 3, spacing: "sm"},
|
||||
{ maxWidth: 'lg', cols: 4, spacing: 'md'}
|
||||
]}
|
||||
|
||||
>
|
||||
{ locations.map((location, idx) =>
|
||||
<LocationCard location={location} idx={idx}></LocationCard>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
import React, {useState, useEffect } from 'react';
|
||||
import { useAtom } from 'jotai'
|
||||
import { serverConfigAtom } from '../../../state/state';
|
||||
import { HiPlus } from 'react-icons/hi'
|
||||
import { Loader, Center, SimpleGrid, Title, Group, Button } from '@mantine/core'
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNotifications } from '@mantine/notifications';
|
||||
|
||||
|
||||
import { backendAPI } from '../../../services/backend-api';
|
||||
import LocationCard from '../../cards/LocationCard';
|
||||
|
||||
|
||||
function LocationsPage() {
|
||||
// const [opened, setOpened] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [locations, setLocations] = useState([])
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
|
||||
let navigate = useNavigate();
|
||||
|
||||
|
||||
|
||||
const notifications = useNotifications();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log("LOADING LOCATIONS PAGE!")
|
||||
setIsLoading(true)
|
||||
async function fetchSettings() {
|
||||
backendAPI.get('/locations').then(results => {
|
||||
console.log("CONFIG IN LOCATIONS: ", serverConfig)
|
||||
console.log("LOCATIONS: ", results.data)
|
||||
setLocations(results.data)
|
||||
setIsLoading(false)
|
||||
}).catch(err => {
|
||||
notifications.showNotification({
|
||||
title: 'Backend Notice',
|
||||
message: `Failed to fetch locations from backend! ${err}`,
|
||||
autoClose: false,
|
||||
color: "red",
|
||||
})
|
||||
setIsLoading(false)
|
||||
})
|
||||
|
||||
}
|
||||
fetchSettings();
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center>{ isLoading && <Loader size="xl" variant="bars" />}</Center>
|
||||
<Center><Title order={1}>Locations</Title></Center>
|
||||
<Button mb="lg" leftIcon={<HiPlus />} onClick={(e) => {navigate("/locations/new")}}>Add New Location</Button>
|
||||
<SimpleGrid
|
||||
spacing="md"
|
||||
cols={4}
|
||||
breakpoints={[
|
||||
{ maxWidth: 'sm', cols: 1, spacing: 'md'},
|
||||
{ maxWidth: 'md', cols: 3, spacing: "sm"},
|
||||
{ maxWidth: 'lg', cols: 4, spacing: 'md'}
|
||||
]}
|
||||
|
||||
>
|
||||
{ locations.map((location, idx) =>
|
||||
<LocationCard location={location} idx={idx}></LocationCard>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default LocationsPage;
|
@@ -1,166 +1,166 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import { Text, Title, TextInput, Button, NumberInput, Textarea, Grid, Group, useMantineTheme, MantineTheme, Image, SimpleGrid } from '@mantine/core'
|
||||
import { DatePicker } from '@mantine/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useAtom } from 'jotai';
|
||||
import { serverConfigAtom } from '../../state/state'
|
||||
import { backendAPI } from '../../services/backend-api';
|
||||
import { IMAGE_MIME_TYPE } from '@mantine/dropzone';
|
||||
import CustomDropZone from '../CustomDropZone';
|
||||
|
||||
|
||||
|
||||
function LocationForm(props) {
|
||||
const {location, modify: bool} = props
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
// Cover Photo
|
||||
const [coverPhoto, setCoverPhoto] = useState(null)
|
||||
// Additional Photos
|
||||
const [additionalPhotos, setAdditionalPhotos] = useState([])
|
||||
|
||||
const theme = useMantineTheme();
|
||||
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
Name: '',
|
||||
Description: '',
|
||||
Notes: '',
|
||||
Address: '',
|
||||
SquareFeet: '',
|
||||
Latitude: '',
|
||||
Longitude: '',
|
||||
DatePurchased: '',
|
||||
PurchasePrice: '',
|
||||
CurrentValue: '',
|
||||
CoverPhoto: '',
|
||||
AdditionalPhotos: [],
|
||||
},
|
||||
|
||||
validate: {
|
||||
Name: (value) => (/^\S+/.test(value) ? null : 'Invalid Name')
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
const submitNewLocation = (values) => {
|
||||
console.log("VALUES: ", values)
|
||||
// let additionalPhotos = values.AdditionalPhotos
|
||||
let formData = new FormData()
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
if (key === "AdditionalPhotos" && value.length > 0) {
|
||||
value.forEach(photo => {
|
||||
console.log("PHOTO VALUE: ", photo)
|
||||
formData.append(key, photo)
|
||||
});
|
||||
}
|
||||
formData.append(key, value)
|
||||
}
|
||||
backendAPI.post("/locations/new", formData).then((result) => {
|
||||
console.log("STATUS: ", result.status)
|
||||
console.log("result: ", result.data)
|
||||
}).catch(err => {
|
||||
console.log("Error adding new location!", err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const handleFileAccept = (files) => {
|
||||
form.setFieldValue('CoverPhoto', files[0])
|
||||
const imageUrl = URL.createObjectURL(files[0]);
|
||||
setCoverPhoto(
|
||||
<Image
|
||||
key={"coverPhoto"}
|
||||
src={imageUrl}
|
||||
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const handleAdditionalPhotos = (files) => {
|
||||
setAdditionalPhotos(oldPhotos => [...oldPhotos, ...files])
|
||||
// setAdditionalPhotos(files)
|
||||
console.log("FORM VALUES: ",form.values)
|
||||
const oldValues = form.values['AdditionalPhotos']
|
||||
form.setFieldValue('AdditionalPhotos', [...oldValues, ...files])
|
||||
console.log("NEW FORM VALUES: ", form.values)
|
||||
|
||||
}
|
||||
|
||||
const previews = additionalPhotos.map((file, index) => {
|
||||
console.log("FILE IS: ", file)
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
return (
|
||||
<Image
|
||||
key={index}
|
||||
src={imageUrl}
|
||||
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Location Form</Title>
|
||||
<form onSubmit={form.onSubmit((values) => submitNewLocation(values))}>
|
||||
<Grid>
|
||||
|
||||
<Grid.Col md={6} lg={4} sm={12} xs={12}>
|
||||
<TextInput label="Location Name" value={form.values.Name} required {...form.getInputProps('Name')}/>
|
||||
<TextInput label="Description" value={form.values.Description} {...form.getInputProps('Description')} />
|
||||
<Textarea label="Location Notes" value={form.values.Notes} {...form.getInputProps('Notes')}/>
|
||||
<TextInput label="Street Address" value={form.values.Address} {...form.getInputProps('Address')} />
|
||||
<NumberInput label="Square Feet" value={form.values.SquareFeet} {...form.getInputProps('SquareFeet')} />
|
||||
<TextInput label="Latitude" value={form.values.Latitude} {...form.getInputProps('Latitude')} />
|
||||
<TextInput label="Longitude" value={form.values.Longitude} {...form.getInputProps('Longitude')}/>
|
||||
<DatePicker
|
||||
label="Date Purchased"
|
||||
value={form.values.DatePurchased}
|
||||
maxDate={dayjs(new Date()).add(1, 'days').toDate()}
|
||||
{...form.getInputProps('DatePurchased')}
|
||||
/>
|
||||
<TextInput label="Purchase Price" value={form.values.PurchasePrice} {...form.getInputProps('PurchasePrice')}/>
|
||||
<TextInput label="Current Value" value={form.values.CurrentValue} {...form.getInputProps('CurrentValue')}/>
|
||||
<Group><Title order={4}>Location Cover Photo </Title><Text color="red">*</Text></Group>
|
||||
|
||||
{coverPhoto ?
|
||||
coverPhoto
|
||||
:
|
||||
<CustomDropZone
|
||||
uploadText1={"Drag Image Here or click to select File"}
|
||||
uploadText2={"Single File, max size: 5mb"}
|
||||
uploadFormat={IMAGE_MIME_TYPE}
|
||||
maxSize={3 * 1024 ** 2}
|
||||
multipleFiles={false}
|
||||
returnFiles={handleFileAccept}
|
||||
/>
|
||||
}
|
||||
<Title order={4}>Additional Location Photos</Title>
|
||||
<CustomDropZone
|
||||
uploadText1={"Drag Images Here or click to select Files"}
|
||||
uploadText2={"Multiple Files, max size per file: 5mb"}
|
||||
uploadFormat={IMAGE_MIME_TYPE}
|
||||
maxSize={3 * 1024 ** 2}
|
||||
multipleFiles={true}
|
||||
returnFiles={handleAdditionalPhotos}
|
||||
/>
|
||||
<SimpleGrid
|
||||
cols={4}
|
||||
breakpoints={[{ maxWidth: 'sm', cols: 1 }]}
|
||||
mt={previews.length > 0 ? 'xl' : 0}
|
||||
>
|
||||
{previews}
|
||||
</SimpleGrid>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</form>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default LocationForm;
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import { Text, Title, TextInput, Button, NumberInput, Textarea, Grid, Group, useMantineTheme, MantineTheme, Image, SimpleGrid } from '@mantine/core'
|
||||
import { DatePicker } from '@mantine/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useAtom } from 'jotai';
|
||||
import { serverConfigAtom } from '../../../state/state'
|
||||
import { backendAPI } from '../../../services/backend-api';
|
||||
import { IMAGE_MIME_TYPE } from '@mantine/dropzone';
|
||||
import CustomDropZone from '../../CustomDropZone';
|
||||
|
||||
|
||||
|
||||
function NewLocationForm(props) {
|
||||
const {location, modify: bool} = props
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [serverConfig] = useAtom(serverConfigAtom)
|
||||
// Cover Photo
|
||||
const [coverPhoto, setCoverPhoto] = useState(null)
|
||||
// Additional Photos
|
||||
const [additionalPhotos, setAdditionalPhotos] = useState([])
|
||||
|
||||
const theme = useMantineTheme();
|
||||
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
Name: '',
|
||||
Description: '',
|
||||
Notes: '',
|
||||
Address: '',
|
||||
SquareFeet: '',
|
||||
Latitude: '',
|
||||
Longitude: '',
|
||||
DatePurchased: '',
|
||||
PurchasePrice: '',
|
||||
CurrentValue: '',
|
||||
CoverPhoto: '',
|
||||
AdditionalPhotos: [],
|
||||
},
|
||||
|
||||
validate: {
|
||||
Name: (value) => (/^\S+/.test(value) ? null : 'Invalid Name')
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
const submitNewLocation = (values) => {
|
||||
console.log("VALUES: ", values)
|
||||
// let additionalPhotos = values.AdditionalPhotos
|
||||
let formData = new FormData()
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
if (key === "AdditionalPhotos" && value.length > 0) {
|
||||
value.forEach(photo => {
|
||||
console.log("PHOTO VALUE: ", photo)
|
||||
formData.append(key, photo)
|
||||
});
|
||||
}
|
||||
formData.append(key, value)
|
||||
}
|
||||
backendAPI.post("/locations/new", formData).then((result) => {
|
||||
console.log("STATUS: ", result.status)
|
||||
console.log("result: ", result.data)
|
||||
}).catch(err => {
|
||||
console.log("Error adding new location!", err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const handleFileAccept = (files) => {
|
||||
form.setFieldValue('CoverPhoto', files[0])
|
||||
const imageUrl = URL.createObjectURL(files[0]);
|
||||
setCoverPhoto(
|
||||
<Image
|
||||
key={"coverPhoto"}
|
||||
src={imageUrl}
|
||||
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const handleAdditionalPhotos = (files) => {
|
||||
setAdditionalPhotos(oldPhotos => [...oldPhotos, ...files])
|
||||
// setAdditionalPhotos(files)
|
||||
console.log("FORM VALUES: ",form.values)
|
||||
const oldValues = form.values['AdditionalPhotos']
|
||||
form.setFieldValue('AdditionalPhotos', [...oldValues, ...files])
|
||||
console.log("NEW FORM VALUES: ", form.values)
|
||||
|
||||
}
|
||||
|
||||
const previews = additionalPhotos.map((file, index) => {
|
||||
console.log("FILE IS: ", file)
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
return (
|
||||
<Image
|
||||
key={index}
|
||||
src={imageUrl}
|
||||
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Location Form</Title>
|
||||
<form onSubmit={form.onSubmit((values) => submitNewLocation(values))}>
|
||||
<Grid>
|
||||
|
||||
<Grid.Col md={6} lg={4} sm={12} xs={12}>
|
||||
<TextInput label="Location Name" value={form.values.Name} required {...form.getInputProps('Name')}/>
|
||||
<TextInput label="Description" value={form.values.Description} {...form.getInputProps('Description')} />
|
||||
<Textarea label="Location Notes" value={form.values.Notes} {...form.getInputProps('Notes')}/>
|
||||
<TextInput label="Street Address" value={form.values.Address} {...form.getInputProps('Address')} />
|
||||
<NumberInput label="Square Feet" value={form.values.SquareFeet} {...form.getInputProps('SquareFeet')} />
|
||||
<TextInput label="Latitude" value={form.values.Latitude} {...form.getInputProps('Latitude')} />
|
||||
<TextInput label="Longitude" value={form.values.Longitude} {...form.getInputProps('Longitude')}/>
|
||||
<DatePicker
|
||||
label="Date Purchased"
|
||||
value={form.values.DatePurchased}
|
||||
maxDate={dayjs(new Date()).add(1, 'days').toDate()}
|
||||
{...form.getInputProps('DatePurchased')}
|
||||
/>
|
||||
<TextInput label="Purchase Price" value={form.values.PurchasePrice} {...form.getInputProps('PurchasePrice')}/>
|
||||
<TextInput label="Current Value" value={form.values.CurrentValue} {...form.getInputProps('CurrentValue')}/>
|
||||
<Group><Title order={4}>Location Cover Photo </Title><Text color="red">*</Text></Group>
|
||||
|
||||
{coverPhoto ?
|
||||
coverPhoto
|
||||
:
|
||||
<CustomDropZone
|
||||
uploadText1={"Drag Image Here or click to select File"}
|
||||
uploadText2={"Single File, max size: 5mb"}
|
||||
uploadFormat={IMAGE_MIME_TYPE}
|
||||
maxSize={3 * 1024 ** 2}
|
||||
multipleFiles={false}
|
||||
returnFiles={handleFileAccept}
|
||||
/>
|
||||
}
|
||||
<Title order={4}>Additional Location Photos</Title>
|
||||
<CustomDropZone
|
||||
uploadText1={"Drag Images Here or click to select Files"}
|
||||
uploadText2={"Multiple Files, max size per file: 5mb"}
|
||||
uploadFormat={IMAGE_MIME_TYPE}
|
||||
maxSize={3 * 1024 ** 2}
|
||||
multipleFiles={true}
|
||||
returnFiles={handleAdditionalPhotos}
|
||||
/>
|
||||
<SimpleGrid
|
||||
cols={4}
|
||||
breakpoints={[{ maxWidth: 'sm', cols: 1 }]}
|
||||
mt={previews.length > 0 ? 'xl' : 0}
|
||||
>
|
||||
{previews}
|
||||
</SimpleGrid>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</form>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default NewLocationForm;
|
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Group, Box, Collapse, ThemeIcon, Text, UnstyledButton, createStyles } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import { Group, Box, Collapse, ThemeIcon, UnstyledButton, createStyles } from '@mantine/core';
|
||||
import { BiChevronLeft, BiChevronRight } from 'react-icons/bi';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import { NoEncryption } from '@material-ui/icons';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const useStyles = createStyles((theme, _params, getRef) => {
|
||||
const icon = getRef('icon');
|
||||
@@ -61,7 +60,6 @@ const useStyles = createStyles((theme, _params, getRef) => {
|
||||
|
||||
|
||||
export function LinksGroup({ icon: Icon, label, link, initiallyOpened, links }) {
|
||||
const mypath = useLocation()
|
||||
const { classes, theme, cx } = useStyles();
|
||||
const hasLinks = Array.isArray(links);
|
||||
const [opened, setOpened] = useState(initiallyOpened || false);
|
||||
@@ -78,9 +76,6 @@ export function LinksGroup({ icon: Icon, label, link, initiallyOpened, links })
|
||||
{link.label}
|
||||
</NavLink>
|
||||
));
|
||||
|
||||
console.log("TOP LINK: ", link)
|
||||
|
||||
if (link) {
|
||||
return (
|
||||
<>
|
||||
|
@@ -1,3 +1,17 @@
|
||||
body {
|
||||
/* body {
|
||||
margin: 0px;
|
||||
} */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import './css/custom.css'
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { NotificationsProvider } from '@mantine/notifications';
|
||||
import { Notifications } from './Notifications.js'
|
||||
import Notifications from './components/Notifications.js'
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
|
||||
//setup api
|
||||
//const backendPort = process.env.REACT_APP_BACKEND_PORT
|
||||
@@ -11,10 +13,12 @@ import { Notifications } from './Notifications.js'
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<NotificationsProvider>
|
||||
<Notifications />
|
||||
<App />
|
||||
</NotificationsProvider>
|
||||
<MantineProvider withNormalizeCSS withGlobalStyles>
|
||||
<NotificationsProvider>
|
||||
<Notifications />
|
||||
<App />
|
||||
</NotificationsProvider>
|
||||
</MantineProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
@@ -15,5 +15,5 @@ export const locationFilterAtom = atom({})
|
||||
export const roomFilterAtom = atom({})
|
||||
|
||||
// Notification history and notification stack
|
||||
export const notificationHistory = atom([])
|
||||
export const notifications = atomWithReset([])
|
||||
export const notificationHistoryAtom = atom([])
|
||||
export const notificationQueueAtom = atomWithReset([])
|
||||
|
Reference in New Issue
Block a user