💅
This commit is contained in:
@@ -47,6 +47,10 @@
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.dashboard-form {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.ant-card {
|
||||
margin: 1rem;
|
||||
}
|
||||
@@ -54,3 +58,12 @@
|
||||
.ant-card img {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.loading-bar-container {
|
||||
background: #282c34;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
background: green;
|
||||
}
|
||||
|
@@ -1,23 +1,42 @@
|
||||
import { Client } from '../types'
|
||||
import axios from 'axios'
|
||||
import { message } from 'antd'
|
||||
import { Status } from '../components/StatusChip'
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const dev = process.env.NODE_ENV === 'development'
|
||||
|
||||
if (dev) {
|
||||
const host = 'http://192.168.1.107:5000'
|
||||
axios.defaults.baseURL = host
|
||||
}
|
||||
|
||||
const mock = false
|
||||
|
||||
export const createClient = async (body: Omit<Client, 'has_photos'>) => {
|
||||
if (mock) return 'test'
|
||||
const res = await axios.post<{ client_id: string }>(`/api/clients`, body)
|
||||
return res.data.client_id
|
||||
}
|
||||
|
||||
export const getClient = async (id: string): Promise<Client> => {
|
||||
if (mock)
|
||||
return {
|
||||
name: 'Test Client',
|
||||
has_photos: false,
|
||||
email: 'test@test.test',
|
||||
phone: 1234567890,
|
||||
}
|
||||
const res = await axios.get<Client>(`/api/clients/${id}`)
|
||||
return res.data
|
||||
}
|
||||
export const startSession = async (clientId: string) => {
|
||||
const res = await axios.post(`/api/clients/${clientId}/session`)
|
||||
return res.data // session data
|
||||
try {
|
||||
const res = await axios.post(`/api/clients/${clientId}/session`)
|
||||
return res.data
|
||||
} catch (err) {
|
||||
message.error('Something went wrong, check connection with the machine')
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
export const getSession = async (clientId: string) => {
|
||||
@@ -38,6 +57,11 @@ export const restartSession = async (clientId: string) => {
|
||||
|
||||
// TOOD: Get status
|
||||
|
||||
export const getStatus = async (): Promise<Status> => {
|
||||
const res = await axios.get<{ status: Status }>('/api/status')
|
||||
return res.data.status
|
||||
}
|
||||
|
||||
// Someday
|
||||
|
||||
export const getClients = async (): Promise<Client[]> => {
|
||||
|
@@ -11,16 +11,9 @@ export const SessionPictures = ({ clientId }: Props) => {
|
||||
const [activeUrl, setActiveUrl] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const handleSync = async () => {
|
||||
message.info('Syncing photos...')
|
||||
const { photos } = await getSession(clientId)
|
||||
if (photos.length) setUrls(photos)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const get = async () => {
|
||||
if (urls && urls.length >= 89 * 2) {
|
||||
setLoading(false)
|
||||
if (urls && urls.length >= 89 * 1) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,23 +46,25 @@ export const SessionPictures = ({ clientId }: Props) => {
|
||||
width="100%"
|
||||
onClick={closeModal}
|
||||
src={`${host}/output/${clientId}/${activeUrl}`}
|
||||
alt="large image"
|
||||
alt="large modal"
|
||||
></img>
|
||||
</Modal>
|
||||
<PageHeader
|
||||
title="Session Pictures"
|
||||
subTitle={`${urls.length}/${89 * 2} loaded`}
|
||||
></PageHeader>
|
||||
subTitle={`${urls.length}/${89 * 1} loaded`}
|
||||
>
|
||||
<div className="loading-bar-container">
|
||||
<div className="loading-bar"></div>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<div className="photo-wall">
|
||||
{urls ? (
|
||||
urls
|
||||
.sort((a, b) => a.split('_')[0].localeCompare(b.split('_')[0]))
|
||||
.map((src) => (
|
||||
<Card className="photo" title={src.split('_')[0]}>
|
||||
<Card key={src} className="photo" title={src.split('_')[0]}>
|
||||
<img
|
||||
onClick={() => setActiveUrl(src)}
|
||||
key={src}
|
||||
id={src}
|
||||
src={`${host}/output/${clientId}/${src}`}
|
||||
alt="lol"
|
||||
/>
|
47
client/src/components/StatusChip.tsx
Normal file
47
client/src/components/StatusChip.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Tag } from 'antd'
|
||||
import { PresetColorType } from 'antd/lib/_util/colors'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getStatus } from '../api'
|
||||
|
||||
export enum Status {
|
||||
'Standing By...',
|
||||
'Warming Up...',
|
||||
'Capturing Photo',
|
||||
'Capturing Grid',
|
||||
'Writing To Disk',
|
||||
'Downloading!',
|
||||
}
|
||||
|
||||
const colors: Partial<PresetColorType>[] = [
|
||||
'lime',
|
||||
'gold',
|
||||
'volcano',
|
||||
'magenta',
|
||||
'geekblue',
|
||||
]
|
||||
|
||||
type Props = {
|
||||
poll: boolean
|
||||
}
|
||||
|
||||
export const StatusChip = ({ poll }: Props) => {
|
||||
const [status, setStatus] = useState<Status>(Status['Standing By...'])
|
||||
|
||||
useEffect(() => {
|
||||
const get = async () => {
|
||||
if (!poll) return
|
||||
const status = await getStatus()
|
||||
setStatus(status)
|
||||
}
|
||||
|
||||
const interval = setInterval(get, 1000 / 4)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Tag color={colors[status]} style={{ display: 'flex' }}>
|
||||
<span style={{ margin: 'auto' }}>{Status[status]}</span>
|
||||
</Tag>
|
||||
)
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
import { Button, Divider, message, PageHeader } from 'antd'
|
||||
import { Button, Divider, Form, Input, message, PageHeader, Row } from 'antd'
|
||||
import FormItem from 'antd/lib/form/FormItem'
|
||||
import { Store } from 'antd/lib/form/interface'
|
||||
import { Content } from 'antd/lib/layout/layout'
|
||||
import React, { FormEvent } from 'react'
|
||||
import { useState } from 'react'
|
||||
@@ -8,21 +10,11 @@ import { createClient } from '../api'
|
||||
export const Dashboard = () => {
|
||||
const history = useHistory()
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [phone, setPhone] = useState('')
|
||||
|
||||
const handleReset = () => {
|
||||
//
|
||||
setName('')
|
||||
setEmail('')
|
||||
setPhone('')
|
||||
}
|
||||
const handleReset = () => {}
|
||||
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (phone.length < 10) {
|
||||
const handleSubmit = async (values: Store) => {
|
||||
if (values.phone.length < 10) {
|
||||
// helpful message
|
||||
message.error('Check all fields!')
|
||||
setError('Phone number needs to be a length of at least 10')
|
||||
@@ -30,9 +22,9 @@ export const Dashboard = () => {
|
||||
}
|
||||
|
||||
const client_id = await createClient({
|
||||
name,
|
||||
email,
|
||||
phone: parseInt(phone),
|
||||
name: values.name,
|
||||
email: values.email,
|
||||
phone: parseInt(values.phone.replace(/\D/g, '')),
|
||||
})
|
||||
|
||||
history.push(`/sessions/${client_id}`)
|
||||
@@ -45,42 +37,31 @@ export const Dashboard = () => {
|
||||
subTitle="Enter the name, email and phone number of the subject"
|
||||
></PageHeader>
|
||||
<Divider />
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="name">
|
||||
Name:
|
||||
<input
|
||||
minLength={3}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
name="name"
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="email">
|
||||
Email:
|
||||
<input
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
type="email"
|
||||
name="email"
|
||||
/>
|
||||
</label>
|
||||
<label htmlFor="phone">
|
||||
Phone:
|
||||
<input
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
type="tel"
|
||||
name="phone"
|
||||
/>
|
||||
</label>
|
||||
<Button danger onClick={handleReset}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button htmlType="submit" type="primary">
|
||||
Start Session
|
||||
</Button>
|
||||
<Form
|
||||
className="dashboard-form"
|
||||
onFinish={handleSubmit}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
>
|
||||
<FormItem label="name" name="name">
|
||||
<Input minLength={3} />
|
||||
</FormItem>
|
||||
<FormItem label="email" name="email">
|
||||
<Input type="email" />
|
||||
</FormItem>
|
||||
<FormItem label="phone" name="phone">
|
||||
<Input type="tel" minLength={10} />
|
||||
</FormItem>
|
||||
<Row justify="space-between">
|
||||
<Button danger onClick={handleReset}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button htmlType="submit" type="primary">
|
||||
Start Session
|
||||
</Button>
|
||||
</Row>
|
||||
{error && <p className="error">{error}</p>}
|
||||
</form>
|
||||
</Form>
|
||||
</Content>
|
||||
)
|
||||
}
|
||||
|
@@ -1,16 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { RouteComponentProps, useHistory } from 'react-router-dom'
|
||||
import { getClient, killSession, restartSession, startSession } from '../api'
|
||||
import { SessionPictures } from './SessionPictures'
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
message,
|
||||
PageHeader,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Tag,
|
||||
} from 'antd'
|
||||
import { SessionPictures } from '../components/SessionPictures'
|
||||
import { StatusChip } from '../components/StatusChip'
|
||||
|
||||
import { Button, Divider, message, Popconfirm, Row, Typography } from 'antd'
|
||||
import { Content } from 'antd/lib/layout/layout'
|
||||
import { Client } from '../types'
|
||||
|
||||
@@ -59,50 +53,61 @@ export const Session = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<PageHeader
|
||||
ghost={false}
|
||||
onBack={() => history.goBack()}
|
||||
title={`Session for ${clientId}`}
|
||||
tags={active ? <Tag color="lime">Active</Tag> : <Tag>Inactive</Tag>}
|
||||
subTitle={`session has ${active ? 'started' : 'not started'}`}
|
||||
extra={[
|
||||
<Button
|
||||
key="startsession"
|
||||
disabled={active}
|
||||
type="primary"
|
||||
onClick={handleStartSession}
|
||||
>
|
||||
Capture
|
||||
</Button>,
|
||||
<Popconfirm
|
||||
key="retry"
|
||||
title="Re-capture set?"
|
||||
onConfirm={handleRestartSession}
|
||||
>
|
||||
<Button type="default" disabled={!active}>
|
||||
Retry Capture
|
||||
</Button>
|
||||
</Popconfirm>,
|
||||
<Popconfirm
|
||||
key="nuke"
|
||||
title="Delete all photos and return to dashboard?"
|
||||
onConfirm={handleNuke}
|
||||
>
|
||||
<Button danger disabled={!active}>
|
||||
Abort Session
|
||||
</Button>
|
||||
</Popconfirm>,
|
||||
<Button
|
||||
key="finish"
|
||||
ghost
|
||||
type="primary"
|
||||
disabled={!active}
|
||||
onClick={handleExit}
|
||||
>
|
||||
Finish Session
|
||||
</Button>,
|
||||
]}
|
||||
></PageHeader>
|
||||
<Row justify="center">
|
||||
<Typography.Title>Client: {client?.name}</Typography.Title>
|
||||
</Row>
|
||||
|
||||
<Row justify="space-around" style={{ width: '60%', margin: 'auto' }}>
|
||||
<Typography.Text>
|
||||
<strong>email:</strong> {client?.email}
|
||||
</Typography.Text>
|
||||
<Typography.Text>
|
||||
<strong>phone:</strong> {client?.phone}
|
||||
</Typography.Text>
|
||||
</Row>
|
||||
<Divider />
|
||||
|
||||
<Row justify="center" className="session-header">
|
||||
<Button
|
||||
key="startsession"
|
||||
disabled={active}
|
||||
type="primary"
|
||||
onClick={handleStartSession}
|
||||
>
|
||||
Capture
|
||||
</Button>
|
||||
<Popconfirm
|
||||
disabled={!active}
|
||||
key="retry"
|
||||
title="Re-capture set?"
|
||||
onConfirm={handleRestartSession}
|
||||
>
|
||||
<Button type="default" disabled={!active}>
|
||||
Retry Capture
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Popconfirm
|
||||
key="nuke"
|
||||
disabled={!active}
|
||||
title="Delete all photos and return to dashboard?"
|
||||
onConfirm={handleNuke}
|
||||
>
|
||||
<Button danger disabled={!active}>
|
||||
Abort Session
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Button
|
||||
key="finish"
|
||||
ghost
|
||||
type="primary"
|
||||
disabled={!active}
|
||||
onClick={handleExit}
|
||||
>
|
||||
Finish Session
|
||||
</Button>
|
||||
<StatusChip poll={true} />
|
||||
</Row>
|
||||
|
||||
<Divider />
|
||||
<Row className="controls">
|
||||
{active && <SessionPictures clientId={clientId} />}
|
||||
|
Reference in New Issue
Block a user