|
|
@ -13,6 +13,7 @@ import ProgressBar from 'react-bootstrap/ProgressBar'; |
|
|
|
import Accordion from 'react-bootstrap/Accordion'; |
|
|
|
import Accordion from 'react-bootstrap/Accordion'; |
|
|
|
import ToastContainer from 'react-bootstrap/ToastContainer' |
|
|
|
import ToastContainer from 'react-bootstrap/ToastContainer' |
|
|
|
import Toast from 'react-bootstrap/Toast' |
|
|
|
import Toast from 'react-bootstrap/Toast' |
|
|
|
|
|
|
|
import Nav from 'react-bootstrap/Nav' |
|
|
|
|
|
|
|
|
|
|
|
import { FaCheckCircle } from 'react-icons/fa'; |
|
|
|
import { FaCheckCircle } from 'react-icons/fa'; |
|
|
|
|
|
|
|
|
|
|
@ -20,15 +21,21 @@ import { FaCheckCircle } from 'react-icons/fa'; |
|
|
|
const parse = require('csv-parse/lib/sync') |
|
|
|
const parse = require('csv-parse/lib/sync') |
|
|
|
const axios = require('axios'); |
|
|
|
const axios = require('axios'); |
|
|
|
|
|
|
|
|
|
|
|
const dataSplitters = [0,135,250,388] |
|
|
|
const dataSplitters = [0,128,225,363] |
|
|
|
|
|
|
|
|
|
|
|
const BACKEND_URL = "https://projectdivar.com:4505" |
|
|
|
const BACKEND_URL = "https://projectdivar.com:4505" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const NOTIFICATIONTIMEOUT = 300 //In seconds
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const progress1 = new Audio(process.env.PUBLIC_URL+"/progress1.mp3") |
|
|
|
|
|
|
|
const progress2 = new Audio(process.env.PUBLIC_URL+"/progress2.mp3") |
|
|
|
|
|
|
|
|
|
|
|
function ItemGroup(p) { |
|
|
|
function ItemGroup(p) { |
|
|
|
const { data } = p |
|
|
|
const { data } = p |
|
|
|
const { contributor } = p |
|
|
|
const { contributor } = p |
|
|
|
const { setData1,setData2,setData3,setData4,setLastModified,lastModified } = p |
|
|
|
const { setData1,setData2,setData3,setData4,setLastModified,lastModified } = p |
|
|
|
const [displayData,setDisplayData] = useState([]) |
|
|
|
const [displayData,setDisplayData] = useState([]) |
|
|
|
|
|
|
|
const [lockout,setLockout] = useState(false) |
|
|
|
|
|
|
|
|
|
|
|
useEffect(()=>{ |
|
|
|
useEffect(()=>{ |
|
|
|
setDisplayData([...data].sort((a,b)=>{ |
|
|
|
setDisplayData([...data].sort((a,b)=>{ |
|
|
@ -38,15 +45,6 @@ function ItemGroup(p) { |
|
|
|
})) |
|
|
|
})) |
|
|
|
},[data]) |
|
|
|
},[data]) |
|
|
|
|
|
|
|
|
|
|
|
function findIndex(name,arr) { |
|
|
|
|
|
|
|
for (var i=0;i<arr.length;i++) { |
|
|
|
|
|
|
|
if (arr[i].name===name) { |
|
|
|
|
|
|
|
console.log("Found "+name+" at position "+i) |
|
|
|
|
|
|
|
return i |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(()=>{ |
|
|
|
useEffect(()=>{ |
|
|
|
displayData.forEach((item)=>{ |
|
|
|
displayData.forEach((item)=>{ |
|
|
|
if (document.getElementById("field_"+item.id)) { |
|
|
|
if (document.getElementById("field_"+item.id)) { |
|
|
@ -57,8 +55,15 @@ function ItemGroup(p) { |
|
|
|
|
|
|
|
|
|
|
|
function updateItem(item,target,contributor) { |
|
|
|
function updateItem(item,target,contributor) { |
|
|
|
var correctedVal=Math.min(item.required,target.value); |
|
|
|
var correctedVal=Math.min(item.required,target.value); |
|
|
|
if (correctedVal===item.obtained) {return;} |
|
|
|
if (correctedVal==item.obtained) {return;} |
|
|
|
axios.post(BACKEND_URL+"/updateItem",{obtained:correctedVal,id:item.id,last_modified:new Date(),item_name:item.name,username:contributor,required:item.required,operation:correctedVal==item.required?"FINISH":correctedVal>item.obtained?"INCREASE":"SET",previous_amt:item.obtained}); |
|
|
|
setLockout(true) |
|
|
|
|
|
|
|
axios.post(BACKEND_URL+"/updateItem",{obtained:correctedVal,id:item.id,last_modified:new Date(),item_name:item.name,username:contributor,required:item.required,operation:correctedVal==item.required?"FINISH":correctedVal>item.obtained?"INCREASE":"SET",previous_amt:item.obtained}) |
|
|
|
|
|
|
|
.then((data)=>{ |
|
|
|
|
|
|
|
setLockout(false) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch((err)=>{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return <Accordion.Item className="bg-dark" eventKey={p.akey}> |
|
|
|
return <Accordion.Item className="bg-dark" eventKey={p.akey}> |
|
|
@ -69,7 +74,11 @@ function ItemGroup(p) { |
|
|
|
<img src={"https://xivapi.com"+item.icon}/> {item.name} |
|
|
|
<img src={"https://xivapi.com"+item.icon}/> {item.name} |
|
|
|
</Col> |
|
|
|
</Col> |
|
|
|
<Col> |
|
|
|
<Col> |
|
|
|
<input id={"field_"+item.id} style={{width:"5em"}} defaultValue={item.obtained} className="mt-1 bg-secondary" onChange={(f)=>{ |
|
|
|
<input disabled={lockout} id={"field_"+item.id} style={{width:"5em"}} defaultValue={item.obtained} className="mt-1 bg-secondary" |
|
|
|
|
|
|
|
onKeyDown={(k)=>{ |
|
|
|
|
|
|
|
if (k.key==='Enter') {updateItem(item,document.getElementById("field_"+item.id),contributor)} |
|
|
|
|
|
|
|
}} |
|
|
|
|
|
|
|
onChange={(f)=>{ |
|
|
|
if (f.currentTarget.value>=item.required) {f.currentTarget.blur()}
|
|
|
|
if (f.currentTarget.value>=item.required) {f.currentTarget.blur()}
|
|
|
|
}} onBlur={(f)=>{updateItem(item,f.currentTarget,contributor)}} type="number" min="0" max={item.required}/> / {item.required} {item.required!==item.obtained&&<FaCheckCircle style={{color:"green"}} onClick={(f)=>{ |
|
|
|
}} onBlur={(f)=>{updateItem(item,f.currentTarget,contributor)}} type="number" min="0" max={item.required}/> / {item.required} {item.required!==item.obtained&&<FaCheckCircle style={{color:"green"}} onClick={(f)=>{ |
|
|
|
updateItem(item,{value:item.required},contributor) |
|
|
|
updateItem(item,{value:item.required},contributor) |
|
|
@ -83,6 +92,51 @@ function ItemGroup(p) { |
|
|
|
</Accordion.Item> |
|
|
|
</Accordion.Item> |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function Notification(p) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [show,setShow] = useState(true) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { not } = p |
|
|
|
|
|
|
|
return <Toast key={not.id} show={show} autohide delay={NOTIFICATIONTIMEOUT*1000} onClose={()=>{setShow(false)}} bg={not.operation==="FINISH"?"success":not.operation==="INCREASE"?"primary":"warning"}> |
|
|
|
|
|
|
|
<Toast.Header closeButton={true}> |
|
|
|
|
|
|
|
<span className="me-auto"> |
|
|
|
|
|
|
|
<strong>{not.username}</strong> |
|
|
|
|
|
|
|
{not.operation==="FINISH"?" has finished collecting "+not.required+"/"+not.required+" "+not.item_name+"!": |
|
|
|
|
|
|
|
not.operation==="INCREASE"?" has collected "+not.obtained+"/"+not.required+" "+not.item_name+" (+"+(not.obtained-not.previous_amt)+")" |
|
|
|
|
|
|
|
:" has set "+not.item_name+" to "+not.obtained+"/"+not.required}</span> |
|
|
|
|
|
|
|
</Toast.Header> |
|
|
|
|
|
|
|
</Toast> |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function DarkInput(p){ |
|
|
|
|
|
|
|
const {props}=p; |
|
|
|
|
|
|
|
return <input type="text" className="bg-dark text-white" {...p}></input> |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function SorterApp(){ |
|
|
|
|
|
|
|
const [item1,setItem1] = useState("") |
|
|
|
|
|
|
|
const [item2,setItem2] = useState("") |
|
|
|
|
|
|
|
const [item3,setItem3] = useState("") |
|
|
|
|
|
|
|
const [item4,setItem4] = useState("") |
|
|
|
|
|
|
|
const [item5,setItem5] = useState("") |
|
|
|
|
|
|
|
const [item6,setItem6] = useState("") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return <> |
|
|
|
|
|
|
|
<Row className="text-white"><Col>Item 1:<DarkInput value={item1} onChange={(f)=>{setItem1(f.currentTarget.value);}}></DarkInput></Col><Col><input type="text"></input></Col></Row> |
|
|
|
|
|
|
|
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row> |
|
|
|
|
|
|
|
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row> |
|
|
|
|
|
|
|
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row> |
|
|
|
|
|
|
|
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row> |
|
|
|
|
|
|
|
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row> |
|
|
|
|
|
|
|
</> |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function ListApp(){ |
|
|
|
|
|
|
|
return <> |
|
|
|
|
|
|
|
<h1>List App!</h1> |
|
|
|
|
|
|
|
</> |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function App() { |
|
|
|
function App() { |
|
|
|
|
|
|
|
|
|
|
|
const [data,setData] = useState([]) |
|
|
|
const [data,setData] = useState([]) |
|
|
@ -98,6 +152,14 @@ function App() { |
|
|
|
const [lastModified,setLastModified] = useState(new Date()) |
|
|
|
const [lastModified,setLastModified] = useState(new Date()) |
|
|
|
|
|
|
|
|
|
|
|
const [contributor,setContributor] = useState("") |
|
|
|
const [contributor,setContributor] = useState("") |
|
|
|
|
|
|
|
const [notifications,setNotifications] = useState([]) |
|
|
|
|
|
|
|
const [closedNotifications,setClosedNotifications] = useState([]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [nav,setNav] = useState("main") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function LZ(digits,numb) { |
|
|
|
|
|
|
|
return "0".repeat(digits-String(numb).length)+numb |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
useEffect(()=>{ |
|
|
|
useEffect(()=>{ |
|
|
|
const interval = setInterval(()=>{ |
|
|
|
const interval = setInterval(()=>{ |
|
|
@ -123,6 +185,49 @@ function App() { |
|
|
|
return ()=>clearInterval(interval) |
|
|
|
return ()=>clearInterval(interval) |
|
|
|
},[lastModified]) |
|
|
|
},[lastModified]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(()=>{ |
|
|
|
|
|
|
|
var notificationLastUpdate = new Date(new Date()-(NOTIFICATIONTIMEOUT*1000)) |
|
|
|
|
|
|
|
var largestNotificationID = -1 |
|
|
|
|
|
|
|
var dataCheck=true |
|
|
|
|
|
|
|
const interval = setInterval(()=>{ |
|
|
|
|
|
|
|
if (dataCheck) { |
|
|
|
|
|
|
|
dataCheck=false |
|
|
|
|
|
|
|
notificationLastUpdate = new Date(new Date()-(NOTIFICATIONTIMEOUT*1000)) |
|
|
|
|
|
|
|
axios.get(BACKEND_URL+"/getNotifications?date="+encodeURIComponent(LZ(4,notificationLastUpdate.getUTCFullYear())+"-"+LZ(2,notificationLastUpdate.getUTCMonth()+1)+"-"+LZ(2,notificationLastUpdate.getUTCDate())+" "+LZ(2,notificationLastUpdate.getUTCHours())+":"+LZ(2,notificationLastUpdate.getUTCMinutes())+":"+LZ(2,notificationLastUpdate.getUTCSeconds())+"."+LZ(3,notificationLastUpdate.getUTCMilliseconds())+"+00")) |
|
|
|
|
|
|
|
.then((data)=>{ |
|
|
|
|
|
|
|
if (data.data.length>0) { |
|
|
|
|
|
|
|
console.log("New notification array: "+JSON.stringify(data.data)) |
|
|
|
|
|
|
|
setNotifications(data.data) |
|
|
|
|
|
|
|
var completion=false |
|
|
|
|
|
|
|
var largestID = -1 |
|
|
|
|
|
|
|
for (var dat of data.data) { |
|
|
|
|
|
|
|
if (dat.id>largestNotificationID && dat.operation==="FINISH") { |
|
|
|
|
|
|
|
completion=true |
|
|
|
|
|
|
|
largestID=Math.max(dat.id,largestID) |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} else
|
|
|
|
|
|
|
|
if (dat.id>largestNotificationID) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
largestID=Math.max(dat.id,largestID) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (largestID!==-1) {
|
|
|
|
|
|
|
|
largestNotificationID = largestID |
|
|
|
|
|
|
|
if (completion) {progress2.play()} else {progress1.play()} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.catch((err)=>{ |
|
|
|
|
|
|
|
console.log(err.message) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.then(()=>{ |
|
|
|
|
|
|
|
dataCheck=true |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
},1000) |
|
|
|
|
|
|
|
return ()=>clearInterval(interval) |
|
|
|
|
|
|
|
},[]) |
|
|
|
|
|
|
|
|
|
|
|
const disabled=true |
|
|
|
const disabled=true |
|
|
|
|
|
|
|
|
|
|
|
function downloadData(d,val,max) { |
|
|
|
function downloadData(d,val,max) { |
|
|
@ -205,14 +310,20 @@ function App() { |
|
|
|
<Navbar.Brand href="#home"> |
|
|
|
<Navbar.Brand href="#home"> |
|
|
|
<img src={process.env.PUBLIC_URL+"/favicon.ico"} width="30" height="30" className="d-inline-block align-top" alt="BUN logo"/> BUN Collab App |
|
|
|
<img src={process.env.PUBLIC_URL+"/favicon.ico"} width="30" height="30" className="d-inline-block align-top" alt="BUN logo"/> BUN Collab App |
|
|
|
</Navbar.Brand> |
|
|
|
</Navbar.Brand> |
|
|
|
{contributor.length>0&&<span className="text-light font-weight-light font-italic">Signed in as {contributor}</span>} |
|
|
|
{contributor.length>0&&<> |
|
|
|
|
|
|
|
<Nav.Link style={(nav==="main")?{"color":"lime","text-decoration":"underline"}:{}} onClick={()=>{setNav("main")}}>Main</Nav.Link> |
|
|
|
|
|
|
|
<Nav.Link style={(nav==="sort")?{"color":"lime","text-decoration":"underline"}:{}} onClick={()=>{setNav("sort")}}>Sort Check</Nav.Link> |
|
|
|
|
|
|
|
<Nav.Link style={(nav==="list")?{"color":"lime","text-decoration":"underline"}:{}} onClick={()=>{setNav("list")}}>Grocery List</Nav.Link> |
|
|
|
|
|
|
|
<span className="text-light font-weight-light font-italic">Signed in as {contributor}</span> |
|
|
|
|
|
|
|
</>} |
|
|
|
</Container> |
|
|
|
</Container> |
|
|
|
</Navbar> |
|
|
|
</Navbar> |
|
|
|
<Container> |
|
|
|
<Container> |
|
|
|
{contributor.length===0?<> |
|
|
|
{contributor.length===0?<> |
|
|
|
<input placeHolder="Bun" onKeyDown={(k)=>{if (k.key==='Enter') {setContributor(document.getElementById("username").value)}}} id="username"/> |
|
|
|
<input placeHolder="e.g. Bun" onKeyDown={(k)=>{if (k.key==='Enter') {setContributor(document.getElementById("username").value)}}} id="username"/> |
|
|
|
<button type="Submit" onClick={(f)=>{setContributor(document.getElementById("username").value)}}>Submit</button> |
|
|
|
<button type="Submit" onClick={(f)=>{setContributor(document.getElementById("username").value)}}>Submit</button> |
|
|
|
</>: |
|
|
|
</>: |
|
|
|
|
|
|
|
nav==="main"? |
|
|
|
data.length>0? |
|
|
|
data.length>0? |
|
|
|
<> |
|
|
|
<> |
|
|
|
<Accordion className="bg-dark" defaultActiveKey="0"> |
|
|
|
<Accordion className="bg-dark" defaultActiveKey="0"> |
|
|
@ -242,16 +353,16 @@ function App() { |
|
|
|
} |
|
|
|
} |
|
|
|
</Col> |
|
|
|
</Col> |
|
|
|
</Row> |
|
|
|
</Row> |
|
|
|
|
|
|
|
: |
|
|
|
|
|
|
|
nav==="sort"?<SorterApp></SorterApp>: |
|
|
|
|
|
|
|
nav==="list"?<ListApp></ListApp>: |
|
|
|
|
|
|
|
<></> |
|
|
|
} |
|
|
|
} |
|
|
|
<div style={{pointerEvents:"none",position:"fixed",top:"0px",left:"0px",width:"100%",height:"100%"}}> |
|
|
|
<div style={{pointerEvents:"none",position:"fixed",top:"0px",left:"0px",width:"100%",height:"100%"}}> |
|
|
|
<ToastContainer position="bottom-end"> |
|
|
|
<ToastContainer position="bottom-end"> |
|
|
|
<Toast autohide delay={10000} onClose={()=>{console.log("closing")}} bg="primary"> |
|
|
|
{notifications.map((not)=>{ |
|
|
|
<Toast.Header closeButton={true}> |
|
|
|
return <Notification key={not.id} not={not}/> |
|
|
|
<strong className="me-auto">Testing</strong> |
|
|
|
})} |
|
|
|
<small>11 mins ago</small> |
|
|
|
|
|
|
|
</Toast.Header> |
|
|
|
|
|
|
|
</Toast> |
|
|
|
|
|
|
|
</ToastContainer> |
|
|
|
</ToastContainer> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</Container> |
|
|
|
</Container> |
|
|
|