Template out authorized side of application.

master
Christopher Lindelof 2024-08-27 08:44:33 -04:00
parent ba6a054846
commit 2863bdb149
20 changed files with 730 additions and 57 deletions

View File

@ -8,14 +8,21 @@
"name": "front-end",
"version": "0.1.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-scripts": "5.0.1",
"react-tooltip": "^5.28.0",
"web-vitals": "^2.1.4"
},
"devDependencies": {
@ -2565,6 +2572,101 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz",
"integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.7"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.10",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz",
"integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.7"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz",
"integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==",
"license": "MIT"
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
"integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz",
"integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
"integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.3"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@ -5883,6 +5985,31 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
"integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axobject-query": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz",
@ -6618,6 +6745,12 @@
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
"license": "MIT"
},
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/clean-css": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
@ -15897,6 +16030,12 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@ -16379,6 +16518,20 @@
}
}
},
"node_modules/react-tooltip": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz",
"integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.6.1",
"classnames": "^2.3.0"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View File

@ -3,14 +3,21 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2",
"react-scripts": "5.0.1",
"react-tooltip": "^5.28.0",
"web-vitals": "^2.1.4"
},
"scripts": {

View File

@ -1,19 +1,25 @@
import React from "react";
import React, {Component} from "react";
import './main.css'
import { useSelector } from "react-redux";
import { selectAuthenticated } from "./stateSlicers/authenticationSlice";
import FleetManager from "./components/FleetManager";
import Login from "./components/Login";
function App () {
const authed = useSelector(selectAuthenticated);
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
authenticated: true,
userId: 1,
fullName: "Chris Lindelof"
}
}
render() {
return (
(authed)
? <FleetManager />
(this.state.authenticated)
? <FleetManager user={this.state}/>
: <Login />
)
}
export default App;
}

View File

@ -0,0 +1,110 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronUp, faChevronDown, faClose } from "@fortawesome/free-solid-svg-icons";
import React, { Component } from "react";
export default class FuelPurchases extends Component {
constructor(props) {
super(props)
this.state = {
expanded: false,
showModal: true
}
}
toggleExpanded = () => {
this.setState({
expanded: !this.state.expanded
})
}
setShowModal = (value) => {
this.setState({
showModal: value
})
}
render() {
return (
<>
<div className="bg-gray-400 flex flex-row mt-4 px-3 py-1 justify-between border">
<div className="text-xl">Fuel Purchases</div>
<div className="flex flex-row items-center">
<div onClick={() => this.setShowModal(true)} className="mr-2 px-2 py-1 rounded bg-green-400 hover:cursor-pointer">Add</div>
<div className="hover:cursor-pointer" onClick={this.toggleExpanded}>
{
(this.state.expanded)
? <FontAwesomeIcon icon={faChevronUp} />
: <FontAwesomeIcon icon={faChevronDown} />
}
</div>
</div>
</div>
{
(this.state.showModal)
? (
<>
<div
className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none"
>
<div className="relative w-auto my-6 mx-auto min-w-1/2 max-w-3xl">
{/*content*/}
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
{/*header*/}
<div className="flex items-start justify-between p-5 border-b border-solid border-blueGray-200 rounded-t">
<h3 className="text-3xl font-semibold">
Add Fuel Purchase
</h3>
<button
className="p-1 ml-auto bg-transparent border-0 text-black opacity-5 float-right text-3xl leading-none font-semibold outline-none focus:outline-none"
onClick={() => this.setShowModal(false)}
>
<span className="bg-transparent text-black opacity-5 h-6 w-6 text-2xl block outline-none focus:outline-none">
<FontAwesomeIcon icon={faClose} />
</span>
</button>
</div>
{/*body*/}
<div className="relative p-6 flex-auto">
<div className="flex flex-col">
<input type="date"/>
<div>Date</div>
</div>
<div className="flex flex-col">
<input type="number" step="0.001" className="border" />
Fuel Purchased
</div>
<div className="flex flex-col">
<input type="number" step="0.01" className="border" />
Total Cost
</div>
</div>
{/*footer*/}
<div className="flex items-center justify-end p-6 border-t border-solid border-blueGray-200 rounded-b">
<button
className="text-red-500 background-transparent font-bold uppercase px-6 py-2 text-sm outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
type="button"
onClick={() => this.setShowModal(false)}
>
Close
</button>
<button
className="bg-emerald-500 text-white active:bg-emerald-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
type="button"
onClick={() => this.setShowModal(false)}
>
Add
</button>
</div>
</div>
</div>
</div>
<div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
</>
) : null
}
</>
)
}
}

View File

@ -0,0 +1,18 @@
import React, {Component} from "react";
export default class Header extends Component {
render() {
return (
<div className="w-full border flex items-center justify-between px-4 py-2 text-xl">
<div className="font-bold">
Fleet Management
</div>
<div>
{this.props.user.fullName}
</div>
</div>
)
}
}

View File

@ -0,0 +1,27 @@
import React, {Component} from "react";
export default class Sidebar extends Component {
constructor(props) {
super(props)
this.state={
}
}
render() {
var vehicleList = this.props.companyVehicles.map((vehicle, index) => {
return (
<div onClick={() => this.props.selectVehicle(vehicle.id)} key={`vehicleList-${index}`} className={`py-2 border-b pl-4 hover:cursor-pointer ${(this.props.selectedVehicle === vehicle.id) ? 'bg-gray-400 text-white': ''}`}>Vehicle {vehicle.name}</div>
)
})
return (
<div className="w-1/5 flex flex-col border">
{vehicleList}
</div>
)
}
}

View File

@ -0,0 +1,33 @@
import React, {Component} from "react";
import VehicleName from "./VehicleInformation/VehicleName";
import VehicleImage from "./VehicleInformation/VehicleImage";
import Registration from "./VehicleInformation/Registration";
import VehicleDetails from "./VehicleInformation/VehicleDetails";
import ServiceHistory from "./VehicleInformation/ServiceHistory";
import FuelPurchases from "./FuelPurchases";
export default class VehicleInformation extends Component {
render() {
return (
<div className="grow flex flex-col item-center p-4">
<VehicleName selectedVehicleId={this.props.selectedVehicle} />
<div className="flex flex-row mt-4">
<VehicleImage selectedVehicleId={this.props.selectedVehicle} />
<div className="ml-4 flex flex-row justify-between grow">
<VehicleDetails selectedVehicleId={this.props.selectedVehicle} />
<Registration selectedVehicleId={this.props.selectedVehicle} />
</div>
</div>
<FuelPurchases selectedVehicleId={this.props.selectedVehicle} />
<ServiceHistory selectedVehicleId={this.props.selectedVehicle} />
</div>
)
}
}

View File

@ -0,0 +1,42 @@
import React, {Component} from "react";
import EditableTextDisplay from "../../Informational-Components/EditableTextDisplay";
export default class Registration extends Component {
constructor(props){
super(props)
this.state = {
plate: '',
expiration: '',
locale: ''
}
}
editFieldCallBack = (newValue, label) => {
this.setState({
[label]: newValue
})
}
render() {
return (
<div className="flex flex-col w-1/2">
<div className="underline text-xl text-center">Registration</div>
<div className="flex flex-row items-center">
<div>Locale: </div>
<EditableTextDisplay defaultValue={this.state.locale} fieldType="text" label="locale" callback={this.editFieldCallBack} />
</div>
<div className="flex flex-row items-center">
<div>Plate: </div>
<EditableTextDisplay defaultValue={this.state.plate} fieldType="text" label="plate" callback={this.editFieldCallBack} />
</div>
<div className="flex flex-row items-center">
<div>Expiration: </div>
<EditableTextDisplay defaultValue={this.state.expiration} fieldType="date" label="expiration" callback={this.editFieldCallBack} />
</div>
</div>
)
}
}

View File

@ -0,0 +1,47 @@
import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, {Component} from "react";
export default class ServiceHistory extends Component {
constructor(props){
super()
this.state = {
expanded: false
}
}
toggleExpanded = () => {
this.setState({
expanded: !this.state.expanded
})
}
render() {
return (
<>
<div className="bg-gray-400 flex flex-row border mt-4 px-3 py-1 items-center justify-between">
<div className="text-xl">Service History</div>
<div className="flex flex-row items-center">
<div className="mr-2 px-2 py-1 rounded bg-green-400 hover:cursor-pointer">Add</div>
<div className="hover:cursor-pointer" onClick={this.toggleExpanded}>
{
(this.state.expanded)
? <FontAwesomeIcon icon={faChevronUp} />
: <FontAwesomeIcon icon={faChevronDown} />
}
</div>
</div>
</div>
{
(this.state.expanded)
? <div className="flex flex-col border-l border-r">Service History</div>
: null
}
</>
)
}
}

View File

@ -0,0 +1,46 @@
import React, {Component} from "react";
import EditableTextDisplay from "../../Informational-Components/EditableTextDisplay";
export default class VehicleDetails extends Component {
constructor(props) {
super(props)
this.state = {
vin: "1HGBH41JXMN109186",
year: 2017,
make: "Chevrolet",
model: "Silverado"
}
}
editedTextCallback = (newValue, label) => {
this.setState({
[label]: newValue
})
}
render() {
return (
<div className="flex flex-col w-1/2">
<div className="underline text-xl bold text-center mb-2">Information</div>
<div className="flex flex-row items-center">
<div>VIN:</div>
<EditableTextDisplay defaultValue={this.state.vin} fieldType="text" label="vin" callback={this.editedTextCallback}/>
</div>
<div className="flex flex-row items-center">
<div>Year: </div>
<EditableTextDisplay defaultValue={this.state.year} fieldType="number" label="year" callback={this.editedTextCallback} />
</div>
<div className="flex flex-row items-center">
<div>Make: </div>
<EditableTextDisplay defaultValue={this.state.make} fieldType="text" label="make" callback={this.editedTextCallback} />
</div>
<div className="flex flex-row items-center">
<div>Model: </div>
<EditableTextDisplay defaultValue={this.state.model} fieldType="text" label="model" callback={this.editedTextCallback} />
</div>
</div>
)
}
}

View File

@ -0,0 +1,35 @@
import React, { Component } from "react";
import { Tooltip } from 'react-tooltip'
export default class VehicleImage extends Component {
constructor(props) {
super(props)
this.state = {
imageSet: false,
edit: true
}
}
updateImage = () => {
}
render () {
return (
<>
<div onClick={this.updateImage} id="vehicleImage" className="w-48 h-48 p-1 flex flex-col justify-center items-center bg-gray-400 border">
<div className="z-0">{
(this.state.imageSet)
? <img src={require('../../../images/car-silhouet-png-21300.png')} alt="Vehicle" />
: <div>Image Not Set</div>
}</div>
</div>
<Tooltip anchorSelect="#vehicleImage" place="bottom-end">
<div>Click to Edit</div>
</Tooltip>
</>
)
}
}

View File

@ -0,0 +1,72 @@
import React, { Component } from "react"
import Loading from "../../Informational-Components/Loading"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faPenToSquare, faSave } from "@fortawesome/free-solid-svg-icons"
import { Tooltip } from "react-tooltip"
export default class VehicleName extends Component {
constructor(props) {
super(props)
this.state = {
vehicleName: 'Temp Vehicle Name',
vehicleNameEdit: '',
loading: false,
editing: false
}
}
editVehicleName = () => {
this.setState({
vehicleNameEdit: this.state.vehicleName,
editing: true
})
}
updateVehicleName = (event) => {
this.setState({
vehicleNameEdit: event.target.value
})
}
saveVehicleNameChange = () => {
this.setState({
loading: true
}, () => this.setState({
vehicleName: this.state.vehicleNameEdit,
editing: false
}, () => {
this.setState({
loading: false
})
}))
}
render() {
return (
<div className="w-full flex flex-row justify-center text-xl">
{
(this.state.loading)
? <Loading />
: (this.state.editing)
? <div className="flex flex-row items-center">
<input className="px-2 border" type="text" value={this.state.vehicleNameEdit} onChange={this.updateVehicleName}/>
<div id="save" className="pl-2 cursor-pointer" onClick={this.saveVehicleNameChange}><FontAwesomeIcon icon={faSave} /></div>
<Tooltip anchorSelect="#save" place="bottom">Save changes</Tooltip>
</div>
: <div className="flex items-center">
{this.state.vehicleName}
<div id="edit" className="pl-2 hover:cursor-pointer" onClick={this.editVehicleName}>
<FontAwesomeIcon icon={faPenToSquare} />
<Tooltip anchorSelect="#edit" place="bottom">Edit vehicle name</Tooltip>
</div>
</div>
}
</div>
)
}
}

View File

@ -1,5 +1,36 @@
export default function FleetManager() {
import { Component } from "react";
import Header from "./FleetManager-Components/Header";
import Sidebar from "./FleetManager-Components/Sidebar";
import VehicleInformation from "./FleetManager-Components/VehicleInformation";
export default class FleetManager extends Component {
constructor(props) {
super(props)
this.state = {
companyVehicles: [
{id: 1, name: "1"}, {id: 2, name: "2"}, {id: 3, name: "3"}, {id: 4, name: "4"}, {id: 5, name: "5"}
],
selectedVehicle: 3
}
}
updateSelectedVehicle = (selected) => {
this.setState({
selectedVehicle: selected
})
}
render () {
return (
<div></div>
<div className="w-full min-h-screen flex flex-col">
<Header user={this.props.user} />
<div className="grow flex flex-row">
<Sidebar companyVehicles={this.state.companyVehicles} selectedVehicle={this.state.selectedVehicle} selectVehicle={this.updateSelectedVehicle}/>
<VehicleInformation selectedVehicle={this.state.selectedVehicle} />
</div>
</div>
)
}
}

View File

@ -0,0 +1,76 @@
import React, {Component} from "react";
import Loading from "./Loading";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCancel, faSave, faEdit } from "@fortawesome/free-solid-svg-icons";
import { Tooltip } from "react-tooltip";
export default class EditableTextDisplay extends Component {
constructor(props) {
super(props)
this.state = {
editValue: '',
loading: false,
edit: false
}
}
edit = () => {
this.setState({
editValue: this.props.defaultValue,
edit: true
})
}
handleChange = (event) => {
this.setState({
editValue: event.target.value
})
}
cancel = () => {
this.setState({
editValue: '',
edit: false
})
}
propagateChange = () => {
this.props.callback(this.state.editValue, this.props.label);
this.setState({
edit: false
})
}
render () {
return (
<div>
{
(this.state.loading)
? <Loading />
: (this.state.edit)
? <div className="flex flex-row items-center">
<input className="ml-2 border px-2" type={this.props.fieldType} value={this.state.editValue} onChange={this.handleChange} />
<div id={`${this.props.label}Cancel`} className="ml-2" onClick={this.cancel}><FontAwesomeIcon icon={faCancel} /></div>
<div id={`${this.props.label}Save`} className="ml-2" onClick={this.propagateChange}><FontAwesomeIcon icon={faSave} /></div>
<Tooltip anchorSelect={`#${this.props.label}Save`} place="bottom">
Save {this.props.label.toUpperCase()}
</Tooltip>
<Tooltip anchorSelect={`#${this.props.label}Cancel`} place="bottom">
Cancel {this.props.label.toUpperCase()} Edit
</Tooltip>
</div>
: <div className="flex flex-row ml-2">
<div>{this.props.defaultValue}</div>
<div id={`${this.props.label}Edit`} className="ml-2" onClick={this.edit}><FontAwesomeIcon icon={faEdit} /></div>
<Tooltip anchorSelect={`#${this.props.label}Edit`} place="bottom">
Edit {this.props.label.toUpperCase()}
</Tooltip>
</div>
}
</div>
)
}
}

View File

@ -0,0 +1,9 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
export default function Loading() {
return (
<FontAwesomeIcon icon={faSpinner} spin />
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -3,14 +3,11 @@ import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<React.StrictMode>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function

View File

@ -1,29 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
export const authenticationSlice = createSlice({
name: 'auth',
initialState: {
authenticated: false,
userId: null,
fullName: null
},
reducers: {
authenticated: (state, action) => {
state.authenticated = true;
state.userId = action.payload.userId;
state.fullName = action.payload.fullName;
},
logout: (state) => {
state = {
authenticated: false,
userId: null,
fullName: null
}
}
}
})
export const {authenticated, logout} = authenticationSlice.actions
export const selectAuthenticated = (state) => state.authenticated
export default authenticationSlice.reducer

View File

@ -1,7 +0,0 @@
import { configureStore } from '@reduxjs/toolkit'
import authenticationReducer from './stateSlicers/authenticationSlice'
export default configureStore({
reducer: {authenticationReducer},
})