You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
591 lines
21 KiB
591 lines
21 KiB
import './App.css';
|
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
|
|
import { useState,useEffect } from 'react';
|
|
|
|
import Container from 'react-bootstrap/Container';
|
|
import Row from 'react-bootstrap/Row';
|
|
import Col from 'react-bootstrap/Col';
|
|
import Navbar from 'react-bootstrap/Navbar';
|
|
import Button from 'react-bootstrap/Button';
|
|
import ProgressBar from 'react-bootstrap/ProgressBar';
|
|
import Accordion from 'react-bootstrap/Accordion';
|
|
import ToastContainer from 'react-bootstrap/ToastContainer'
|
|
import Toast from 'react-bootstrap/Toast'
|
|
import Nav from 'react-bootstrap/Nav'
|
|
import Spinner from 'react-bootstrap/Spinner';
|
|
|
|
import { FaCheckCircle } from 'react-icons/fa';
|
|
import { IoCheckmarkCircleOutline,IoCloseCircleSharp,IoAlertCircleOutline } from 'react-icons/io5';
|
|
|
|
|
|
const parse = require('csv-parse/lib/sync')
|
|
const axios = require('axios');
|
|
|
|
const dataSplitters = [0,128,225,363]
|
|
|
|
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) {
|
|
const { data } = p
|
|
const { contributor } = p
|
|
const [displayData,setDisplayData] = useState([])
|
|
const [lockout,setLockout] = useState(false)
|
|
|
|
useEffect(()=>{
|
|
setDisplayData([...data].sort((a,b)=>{
|
|
if (b.required===b.obtained&&a.required!==a.obtained) {return -1}
|
|
if (b.required===b.obtained&&a.required===a.obtained) {return a.id-b.id}
|
|
if (b.required!==b.obtained&&a.required!==a.obtained) {return a.id-b.id}
|
|
return 0
|
|
}))
|
|
},[data])
|
|
|
|
useEffect(()=>{
|
|
displayData.forEach((item)=>{
|
|
if (document.getElementById("field_"+item.id)) {
|
|
document.getElementById("field_"+item.id).value=item.obtained
|
|
}
|
|
})
|
|
},[displayData])
|
|
|
|
function updateItem(item,target,contributor) {
|
|
var correctedVal=Math.min(item.required,target.value);
|
|
if (correctedVal===Number(item.obtained)) {return;}
|
|
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===Number(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}>
|
|
<Accordion.Header className="panel-body bg-dark">{p.name}</Accordion.Header>
|
|
<Accordion.Body className="panel-body">
|
|
{displayData.map((item,i,arr)=><Row key={item.id} className={"pb-1 pt-1 text-light"+(Number(item.obtained)===0?" notStarted":Number(item.obtained)===Number(item.required)?" completed":" inProgress")}>
|
|
<Col>
|
|
<img src={"https://xivapi.com"+item.icon} alt={item.name}/> {item.name}
|
|
</Col>
|
|
<Col>
|
|
<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()}
|
|
}} 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)
|
|
}}/>}
|
|
</Col>
|
|
<Col>
|
|
<a style={{position:"relative",top:"8px"}} className="text-muted" href={"https://garlandtools.org/db/#item/"+item.itemid} target="tools">View Item Info</a>
|
|
</Col>
|
|
</Row>)}
|
|
</Accordion.Body>
|
|
</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){
|
|
return <input type="text" className="bg-dark text-white" {...p}></input>
|
|
}
|
|
function DarkSelect(p){
|
|
return <select className="bg-dark text-white" {...p}>{p.children}</select>
|
|
}
|
|
|
|
function ListApp(p){
|
|
|
|
//https://xivapi.com/search?filters=ClassJob.ID=11,RecipeLevelTable.ClassJobLevel%3E=36,RecipeLevelTable.ClassJobLevel%3C=45&page=1
|
|
const {transferItems} = p
|
|
const [groceryList,setGroceryList] = useState([])
|
|
|
|
const crafters=["CRP Carpenter","BSM Blacksmith","ARM Armorer","GSM Goldsmith","LTW Leatherworker","WVR Weaver","ALC Alchemist","CUL Culinarian"]
|
|
const [selectedCrafter,setSelectedCrafter] = useState(0)
|
|
const [minLevel,setMinLevel] = useState(1)
|
|
const [maxLevel,setMaxLevel] = useState(10)
|
|
const [playerInventory,setPlayerInventory] = useState([])
|
|
const [saddlebagInventory,setSaddlebagInventory] = useState([])
|
|
const [r1Inventory,setR1Inventory] = useState([])
|
|
const [r2Inventory,setR2Inventory] = useState([])
|
|
const [r3Inventory,setR3Inventory] = useState([])
|
|
const [r4Inventory,setR4Inventory] = useState([])
|
|
const [r5Inventory,setR5Inventory] = useState([])
|
|
const [r6Inventory,setR6Inventory] = useState([])
|
|
const [r7Inventory,setR7Inventory] = useState([])
|
|
const [r8Inventory,setR8Inventory] = useState([])
|
|
const [r9Inventory,setR9Inventory] = useState([])
|
|
const [checking,setChecking] = useState(false)
|
|
const [lastUpdate,setLastUpdate] = useState(0)
|
|
|
|
const retainerNames = ["Ayayayaya","Kittystorage","Morecatz","Finalretainer","Nowitsabun","Butwhy","Kkittyy","Howdoesanyonenameall","Ayayayayay"];
|
|
|
|
function sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
useEffect(()=>{
|
|
const interval = setInterval(()=>{
|
|
axios.get("http://projectdivar.com/inventoryData?lastUpdate="+lastUpdate)
|
|
.then((data)=>{
|
|
if (data.data.length>0){
|
|
setLastUpdate(new Date().getTime())
|
|
for (var i=0;i<data.data.length;i++){
|
|
var inventory=data.data[i]
|
|
if (i==0){
|
|
var plInven=[]
|
|
var sbInven=[]
|
|
for (var j=0;j<140;j++){
|
|
plInven=[...plInven,inventory[j]]
|
|
}
|
|
for (var j=140;j<inventory.length;j++){
|
|
sbInven=[...sbInven,inventory[j]]
|
|
}
|
|
setPlayerInventory(plInven)
|
|
setSaddlebagInventory(sbInven)
|
|
} else {
|
|
switch(i){
|
|
case 1:{
|
|
setR1Inventory(inventory)
|
|
}break;
|
|
case 2:{
|
|
setR2Inventory(inventory)
|
|
}break;
|
|
case 3:{
|
|
setR3Inventory(inventory)
|
|
}break;
|
|
case 4:{
|
|
setR4Inventory(inventory)
|
|
}break;
|
|
case 5:{
|
|
setR5Inventory(inventory)
|
|
}break;
|
|
case 6:{
|
|
setR6Inventory(inventory)
|
|
}break;
|
|
case 7:{
|
|
setR7Inventory(inventory)
|
|
}break;
|
|
case 8:{
|
|
setR8Inventory(inventory)
|
|
}break;
|
|
case 9:{
|
|
setR9Inventory(inventory)
|
|
}break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.catch((err)=>{})
|
|
},1000)
|
|
return ()=>clearInterval(interval)
|
|
})
|
|
|
|
function submitForm(){
|
|
let classID=Number(selectedCrafter)+8
|
|
let filter="https://xivapi.com/search?filters=ClassJob.ID="+classID+",RecipeLevelTable.ClassJobLevel>="+minLevel+",RecipeLevelTable.ClassJobLevel<="+maxLevel
|
|
let pageCount=0
|
|
let currentPage=1
|
|
let recipeData=[]
|
|
setChecking(true)
|
|
axios.get(encodeURI(filter))
|
|
.then(async(resp)=>{
|
|
let data=resp.data
|
|
let pageCount=data.Pagination.PageTotal
|
|
let results=data.Results
|
|
while (currentPage<=pageCount){
|
|
for (let recipe of results){
|
|
axios.get(encodeURI("https://xivapi.com"+recipe.Url))
|
|
.then((resp)=>{
|
|
let d = resp.data
|
|
recipeData=[d,...recipeData]
|
|
})
|
|
await sleep(250)
|
|
}
|
|
currentPage++;
|
|
if (currentPage>pageCount){
|
|
break;
|
|
}
|
|
await axios.get(encodeURI(filter+"&page="+currentPage))
|
|
.then((resp)=>{
|
|
let d=resp.data
|
|
results=d.Results
|
|
})
|
|
}
|
|
})
|
|
.finally(async()=>{
|
|
//console.log(recipeData)
|
|
//setChecking(false)
|
|
let craftedItems={}
|
|
for(var recipe of recipeData){
|
|
for (let i=0;i<=5;i++){
|
|
var ingredient=recipe["ItemIngredient"+i]
|
|
var amount=recipe["AmountIngredient"+i]
|
|
if (ingredient!==null){
|
|
if (ingredient.Name.includes("Component Materials")||ingredient.Name.includes("Skybuilders'")){continue}
|
|
if (ingredient.Name in craftedItems){
|
|
let oldItem=craftedItems[ingredient.Name];
|
|
oldItem.amt+=Number(amount)
|
|
craftedItems[ingredient.Name]=oldItem
|
|
} else {
|
|
craftedItems[ingredient.Name]={...ingredient,"amt":Number(amount)}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let retainerItems=[[],[],[],[],[],[],[],[],[],[]]
|
|
for (var item of Object.keys(craftedItems)){
|
|
let found=false
|
|
let retainerArray=[r1Inventory,r2Inventory,r3Inventory,r4Inventory,r5Inventory,r6Inventory,r7Inventory,r8Inventory,r9Inventory]
|
|
for (var i=0;i<retainerArray.length;i++){
|
|
for (var j=0;j<retainerArray[i].length;j++){
|
|
let it = retainerArray[i][j]
|
|
if (it.name===item){
|
|
var targetItem={...it}
|
|
targetItem.amt=craftedItems[item].amt
|
|
targetItem.hq=false
|
|
targetItem.slot=j
|
|
retainerItems[i]=[targetItem,...retainerItems[i]]
|
|
found=true
|
|
break;
|
|
}
|
|
}
|
|
if (found){
|
|
break;
|
|
}
|
|
}
|
|
if (found===false){
|
|
console.log(JSON.stringify(craftedItems[item])+" not found. Adding as "+JSON.stringify({"icon":craftedItems[item].Icon,"id":craftedItems[item].ID,"name":craftedItems[item].Name,"amt":craftedItems[item].amt,"hq":false}))
|
|
retainerItems[9]=[{"icon":craftedItems[item].Icon,"id":craftedItems[item].ID,"name":craftedItems[item].Name,"amt":craftedItems[item].amt,"hq":false},...retainerItems[9]]
|
|
}
|
|
}
|
|
for (var i=0;i<retainerItems.length-1;i++){
|
|
retainerItems[i].sort((a,b)=>a.slot-b.slot)
|
|
}
|
|
setGroceryList(retainerItems)
|
|
setChecking(false)
|
|
})
|
|
}
|
|
|
|
function Item(p){
|
|
const {it} = p
|
|
return it.amt>0&&<Row className="text-white">
|
|
<Col><img src={"https://xivapi.com"+it.icon}></img>{it.slot?"["+Math.floor(it.slot/35+1)+"]":""} {it.name} x{it.amt} {it.hq?"(HQ)":""}</Col>
|
|
</Row>
|
|
}
|
|
function RetainerDisplay(p){
|
|
const {id,inventory} = p
|
|
return <>
|
|
<Row className="text-white"><Col><h2>{retainerNames[id]}'s Inventory</h2></Col></Row>
|
|
{inventory.map((item,i)=><Item it={item} key={i}></Item>)}
|
|
</>
|
|
}
|
|
|
|
return <>
|
|
<Row className="text-white">
|
|
<Col xs={4}>
|
|
<DarkSelect value={selectedCrafter} onChange={(f)=>{setSelectedCrafter(f.currentTarget.value)}}>
|
|
{crafters.map((crafter,i)=><option value={i} key={i}>{crafter}</option>)}
|
|
</DarkSelect>
|
|
</Col>
|
|
|
|
<Col>
|
|
Level Range:
|
|
</Col>
|
|
|
|
<Col xs={2}>
|
|
<DarkInput value={minLevel} onChange={(f)=>{setMinLevel(f.currentTarget.value)}}></DarkInput>
|
|
</Col>
|
|
<Col>-</Col>
|
|
<Col xs={2}>
|
|
<DarkInput value={maxLevel} onChange={(f)=>{setMaxLevel(f.currentTarget.value)}}></DarkInput>
|
|
</Col>
|
|
<Col xs={3}>
|
|
<Button onKeyDown={(k)=>{if (k.key==='Enter') {submitForm()}}} disabled={checking} onClick={()=>{submitForm()}}>{checking?<Spinner animation="border"></Spinner>:"Compose"}</Button>
|
|
</Col>
|
|
</Row>
|
|
<Row><span>A</span></Row>
|
|
{groceryList.map((list,i)=>{
|
|
if (list.length>0){
|
|
return <>
|
|
<Row className="text-white"><Col>{(i===9)?<h4 style={{"color":"#AA0000"}}>Not Found:</h4>:<h4>From {retainerNames[i]}:</h4>}</Col></Row>
|
|
<Row className="text-white"><Col>
|
|
<ul>
|
|
{list.map((item,j)=><li><Item it={item} key={j}></Item></li>)}
|
|
</ul>
|
|
</Col></Row>
|
|
</>
|
|
}
|
|
})}
|
|
<Row><span>Inventories</span></Row>
|
|
<Row className="text-white"><Col><h2>Player Inventory</h2></Col></Row>
|
|
{playerInventory.map((item,i)=><Item it={item} key={i}></Item>)}
|
|
<Row className="text-white"><Col><h2>Chocobo Saddlebag Inventory</h2></Col></Row>
|
|
{saddlebagInventory.map((item,i)=><Item it={item} key={i}></Item>)}
|
|
<RetainerDisplay id={0} inventory={r1Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={1} inventory={r2Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={2} inventory={r3Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={3} inventory={r4Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={4} inventory={r5Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={5} inventory={r6Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={6} inventory={r7Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={7} inventory={r8Inventory}></RetainerDisplay>
|
|
<RetainerDisplay id={8} inventory={r9Inventory}></RetainerDisplay>
|
|
</>
|
|
}
|
|
|
|
function App() {
|
|
|
|
const [data,setData] = useState([])
|
|
const [data2,setData2] = useState([])
|
|
const [data3,setData3] = useState([])
|
|
const [data4,setData4] = useState([])
|
|
const [fileData,setFileData] = useState()
|
|
const [update,setUpdate] = useState(true)
|
|
const [succeeded,setSucceeded] = useState(0)
|
|
const [failed,setFailed] = useState(0)
|
|
const [total,setTotal] = useState(0)
|
|
const [lastModified,setLastModified] = useState(new Date())
|
|
|
|
const [contributor,setContributor] = useState("")
|
|
const [notifications,setNotifications] = useState([])
|
|
|
|
const [nav,setNav] = useState("main")
|
|
|
|
const [transferItems,setTransferItems] = useState([])
|
|
|
|
function LZ(digits,numb) {
|
|
return "0".repeat(digits-String(numb).length)+numb
|
|
}
|
|
|
|
useEffect(()=>{
|
|
const interval = setInterval(()=>{
|
|
axios.get(BACKEND_URL+"/lastUpdate")
|
|
.then((data)=>{
|
|
if (new Date(data.data[0].last_modified)>lastModified) {
|
|
console.log("Updating entries... "+[lastModified,data.data[0].last_modified])
|
|
setLastModified(new Date())
|
|
return axios.get("https://projectdivar.com:4505/getData")
|
|
.then((data)=>{
|
|
//setData(data.data)
|
|
setData(data.data.slice(dataSplitters[0],dataSplitters[1]))
|
|
setData2(data.data.slice(dataSplitters[1],dataSplitters[2]))
|
|
setData3(data.data.slice(dataSplitters[2],dataSplitters[3]))
|
|
setData4(data.data.slice(dataSplitters[3],data.data.length))
|
|
})
|
|
}
|
|
})
|
|
.catch((err)=>{
|
|
console.log(err.message)
|
|
})
|
|
},1000)
|
|
return ()=>clearInterval(interval)
|
|
},[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
|
|
|
|
useEffect(()=>{
|
|
if (update) {
|
|
axios.get("https://projectdivar.com:4505/getData")
|
|
.then((data)=>{
|
|
//setData(data.data)
|
|
setData(data.data.slice(dataSplitters[0],dataSplitters[1]))
|
|
setData2(data.data.slice(dataSplitters[1],dataSplitters[2]))
|
|
setData3(data.data.slice(dataSplitters[2],dataSplitters[3]))
|
|
setData4(data.data.slice(dataSplitters[3],data.data.length))
|
|
})
|
|
.catch((err)=>{
|
|
console.log(err.message)
|
|
setData([
|
|
{id:1726,itemid:2,name:"Fire Shard",icon:"/i/020000/020001.png",obtained:694,required:4124},
|
|
{id:1727,itemid:3,name:"Ice Shard",icon:"/i/020000/020003.png",obtained:0,required:1226},
|
|
{id:1728,itemid:4,name:"Wind Shard",icon:"/i/020000/020004.png",obtained:4719,required:4719},
|
|
{id:1729,itemid:5,name:"Earth Shard",icon:"/i/020000/020006.png",obtained:15,required:1764},
|
|
{id:1730,itemid:6,name:"Lightning Shard",icon:"/i/020000/020005.png",obtained:0,required:2374},
|
|
])
|
|
})
|
|
setUpdate(false)
|
|
}
|
|
},[update])
|
|
|
|
useEffect(()=>{
|
|
if (succeeded+failed===total) {
|
|
setUpdate(true)
|
|
}
|
|
},[succeeded,failed,total])
|
|
|
|
useEffect(()=>{
|
|
|
|
const downloadData=(d,val,max)=>{
|
|
if (val<max) {
|
|
axios.get(encodeURI("https://xivapi.com/search?string="+d[val].Item))
|
|
.then((data)=>{
|
|
var results = data.data.Results
|
|
var found=false
|
|
for (var r of results) {
|
|
if (r.Name===d[val].Item&&r.UrlType==="Item") {
|
|
found=true
|
|
//console.log("Found "+r)
|
|
setSucceeded(succeeded+1)
|
|
var dataObj = {
|
|
itemid:r.ID,
|
|
name:r.Name,
|
|
obtained:0,
|
|
required:d[val].Needed,
|
|
icon:r.Icon
|
|
}
|
|
return axios.post(BACKEND_URL+"/setItem",dataObj)
|
|
}
|
|
}
|
|
if (!found) {
|
|
setFailed(failed+1)
|
|
console.log("Could not find "+d[val].Item+"....")
|
|
}
|
|
})
|
|
.then(()=>{
|
|
setTimeout(downloadData(d,val+1,max),250)
|
|
})
|
|
.catch((err)=>{
|
|
setFailed(failed+1)
|
|
})
|
|
}
|
|
}
|
|
|
|
var d = parse(fileData,{columns:true,skip_empty_lines:true})
|
|
console.log(d)
|
|
downloadData(d,0,d.length)
|
|
setTotal(d.length)
|
|
},[fileData,failed,succeeded])
|
|
|
|
return (
|
|
<Container className="bg-dark" fluid>
|
|
<Navbar bg="dark" variant="dark">
|
|
<Container>
|
|
<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
|
|
</Navbar.Brand>
|
|
{contributor.length>0&&<>
|
|
<Nav.Link style={(nav==="main")?{"color":"lime","textDecoration":"underline"}:{}} onClick={()=>{setNav("main")}}>Main</Nav.Link>
|
|
<Nav.Link style={(nav==="list")?{"color":"lime","textDecoration":"underline"}:{}} onClick={()=>{setNav("list")}}>Grocery List</Nav.Link>
|
|
<span className="text-light font-weight-light font-italic">Signed in as {contributor}</span>
|
|
</>}
|
|
</Container>
|
|
</Navbar>
|
|
<Container>
|
|
{contributor.length===0?<>
|
|
<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>
|
|
</>:
|
|
nav==="main"?
|
|
data.length>0?
|
|
<>
|
|
<Accordion className="bg-dark" defaultActiveKey="0">
|
|
<ItemGroup name="Gathering Items" contributor={contributor} akey="0" data={data} lastModified={lastModified} setLastModified={setLastModified} setData={setData} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
|
|
<ItemGroup name="Other Items" contributor={contributor} akey="1" data={data2} lastModified={lastModified} setLastModified={setLastModified} setData={setData2} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
|
|
<ItemGroup name="Pre-crafting" contributor={contributor} akey="2" data={data3} lastModified={lastModified} setLastModified={setLastModified} setData={setData3} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
|
|
<ItemGroup name="Crafting Items" contributor={contributor} akey="3" data={data4} lastModified={lastModified} setLastModified={setLastModified} setData={setData4} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
|
|
</Accordion>
|
|
</>:
|
|
!disabled&&
|
|
<Row>
|
|
<Col>
|
|
{total===0?<caption><label className="buttonLabel" for="uploads">Import List</label><input onChange={(f)=>{
|
|
const reader = new FileReader()
|
|
reader.onload=(ev)=>{
|
|
setFileData(ev.target.result)
|
|
}
|
|
reader.readAsText(f.target.files[0])
|
|
}} style={{opacity:0}} id="uploads" type="file" accept=".txt,.csv"/></caption>:
|
|
<>
|
|
<ProgressBar>
|
|
<ProgressBar animated striped variant="success" now={Math.round(succeeded/total)*100} />
|
|
<ProgressBar animated striped variant="danger" now={Math.round(failed/total)*100} />
|
|
</ProgressBar>
|
|
<div className="text-center">{Math.round(((failed+succeeded)/total)*100)+"%"}</div>
|
|
</>
|
|
}
|
|
</Col>
|
|
</Row>
|
|
:
|
|
nav==="list"?<ListApp transferItems={transferItems}></ListApp>:
|
|
<></>
|
|
}
|
|
<div style={{pointerEvents:"none",position:"fixed",top:"0px",left:"0px",width:"100%",height:"100%"}}>
|
|
<ToastContainer position="bottom-end">
|
|
{notifications.map((not)=>{
|
|
return <Notification key={not.id} not={not}/>
|
|
})}
|
|
</ToastContainer>
|
|
</div>
|
|
</Container>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
export default App;
|
|
|