updating frontend to new standards

This commit is contained in:
2023-01-10 22:49:59 -05:00
parent d8ca168844
commit 472fe457ed
16 changed files with 774 additions and 502 deletions

View File

@@ -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"}

View File

@@ -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>
}
</>
);
}

View File

@@ -1,11 +0,0 @@
function Notifications() {
return(
<></>
)
}
export default Notifications;

View File

@@ -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

View 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

View 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

View File

@@ -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' },
];

View 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
}

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 (
<>

View File

@@ -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;
}

View File

@@ -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')
);

View File

@@ -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([])