Compare commits

..

2 Commits

Author SHA1 Message Date
Dominic Zimmer
309db89f59 Fix minor usability/DOM issues
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-01 13:46:38 +01:00
Dominic Zimmer
abb69f45e3 Implement edit extraData 2026-02-01 13:36:14 +01:00
2 changed files with 40 additions and 17 deletions

View File

@ -246,7 +246,11 @@ dialog > div label {
text-align: center; text-align: center;
} }
&.heading> div { &.heading> div {
overflow: hidden;
background-color: #b6b6b6; background-color: #b6b6b6;
text-align: center; text-align: center;
} }
} }
#dialog-edit-user input.invalid {
color: red;
}

View File

@ -1,7 +1,7 @@
import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import './AdminUI.css'; import './AdminUI.css';
import { createGuestRequest, createPartyRequest, deleteGuestRequest, deletePartyRequest, listGuestsRequest, listPartyRequest, modifyGuestRequest, parseToken, setAllowedExtras } from './partyAdminApi'; import { createGuestRequest, createPartyRequest, deleteGuestRequest, deletePartyRequest, listGuestsRequest, listPartyRequest, modifyGuestRequest, parseToken, setAllowedExtras } from './partyAdminApi';
import { GrammaticalGender, Person, RequestCreateGuest, ResponseCreateParty, ResponseListGuests, ResponseListParties } from './partyAdminApiTypes'; import { GrammaticalGender, Person, RequestCreateGuest, ResponseCreateParty, ResponseListGuests, ResponseListParties, AllowedExtraLengths } from './partyAdminApiTypes';
export const AdminUI: React.FC = () => { export const AdminUI: React.FC = () => {
@ -12,7 +12,6 @@ export const AdminUI: React.FC = () => {
const adminToken = useMemo(() => parseToken(location.href) ?? "", []); const adminToken = useMemo(() => parseToken(location.href) ?? "", []);
const loadPartyList = useCallback(async () => { const loadPartyList = useCallback(async () => {
console.log("load party list");
const listPartyResponse = await listPartyRequest(adminToken); const listPartyResponse = await listPartyRequest(adminToken);
if (selectedParty === undefined) if (selectedParty === undefined)
setSelectedParty(listPartyResponse.length > 0 ? 0 : undefined); setSelectedParty(listPartyResponse.length > 0 ? 0 : undefined);
@ -92,21 +91,21 @@ export const AdminUI: React.FC = () => {
if (adminToken === "") return; if (adminToken === "") return;
loadPartyList(); loadPartyList();
}, [adminToken, loadPartyList]); }, [adminToken, loadPartyList]);
return <> return <>
{editUserData || editingParty ? {editUserData || editingParty ?
<div id="backdrop" onClick={dismissBackdrop} /> <div id="backdrop" onClick={dismissBackdrop} />
: null} : null}
<dialog id="dialog-edit-party"> <dialog id="dialog-edit-party">
<div> <div>
<span className="lg">Edit Parties</span> <h1>Edit Parties</h1>
<div className="dialog-party-table"> <div className="dialog-party-table">
{partyList.map((p, _i)=> <> {partyList.map((p, i)=> <Fragment key={i}>
<b>{p.name}</b> <b>{p.name}</b>
<label htmlFor={`edit-keys-${p.name}`}>ExtraData:</label> <label htmlFor={`edit-keys-${p.name}`}>ExtraData:</label>
<input type="text" id={`payload-${p.name}`} defaultValue={JSON.stringify(p.allowed_extra)} /> <input type="text" id={`payload-${p.name}`} defaultValue={JSON.stringify(p.allowed_extra)} />
<button onClick={() => updateExtras(p.name)}>update</button> <button onClick={() => updateExtras(p.name)}>update</button>
</>)} </Fragment>)}
</div> </div>
<span className="danger-hint">To delete a party, enter <pre>deletethis</pre> and press update!</span> <span className="danger-hint">To delete a party, enter <pre>deletethis</pre> and press update!</span>
<div className="dialog-bottom"> <div className="dialog-bottom">
@ -120,7 +119,7 @@ export const AdminUI: React.FC = () => {
<div> <div>
{editUserData ? {editUserData ?
<> <>
<span className="lg">{"_id" in editUserData ? `Editing ${editUserData.name}` : "New Invitation"}</span> <h1>{"_id" in editUserData ? `Editing ${editUserData.name}` : "New Invitation"}</h1>
<div className="dialog-person-table"> <div className="dialog-person-table">
<label htmlFor="edit-token">Token</label> <label htmlFor="edit-token">Token</label>
<input type="text" id="edit-token" value={editUserData.token} onChange={(e) => { setEditUserData({ ...editUserData, token: e.target.value }) }} /> <input type="text" id="edit-token" value={editUserData.token} onChange={(e) => { setEditUserData({ ...editUserData, token: e.target.value }) }} />
@ -145,6 +144,9 @@ export const AdminUI: React.FC = () => {
<option value="f">f</option> <option value="f">f</option>
<option value="d">d</option> <option value="d">d</option>
</select> </select>
{selectedParty !== undefined && partyList[selectedParty] ?
<ExtraDataTable value={editUserData} setEditUserData={setEditUserData} allowed_extra={partyList[selectedParty].allowed_extra} />
:null}
</div> </div>
</> </>
: "Hab keine user data bekommen."} : "Hab keine user data bekommen."}
@ -183,6 +185,23 @@ export const AdminUI: React.FC = () => {
</> </>
}; };
export const ExtraDataTable : React.FC<{
value: RequestCreateGuest | Person,
setEditUserData: React.Dispatch<React.SetStateAction<RequestCreateGuest | Person | undefined>>,
allowed_extra: AllowedExtraLengths }
> = ({value: editUserData, setEditUserData, allowed_extra}) => {
return <>
<h2 style={{gridColumn:"1/span 2", textAlign: "center"}} >Extras</h2>
{Object.entries(allowed_extra).map(([extra, maxLen], index) =>
<Fragment key={index}>
<label htmlFor={`edit-extra-${extra}`}>{extra}</label>
<input className={((editUserData.extra[extra]??"").length > maxLen ? "invalid" : "")} type="text" id={`edit-extra-${extra}`} value={editUserData.extra[extra]} onChange={(e) => { setEditUserData({ ...editUserData, extra: {...editUserData.extra, [extra]: e.target.value}}) }} />
</Fragment>
)}
</>;
}
export const PartyUI: React.FC<{ party: ResponseCreateParty, adminToken: string, editUser: (user: RequestCreateGuest) => void }> = ({ party, adminToken, editUser }) => { export const PartyUI: React.FC<{ party: ResponseCreateParty, adminToken: string, editUser: (user: RequestCreateGuest) => void }> = ({ party, adminToken, editUser }) => {
const [guests, setGuests] = useState<ResponseListGuests>([]); const [guests, setGuests] = useState<ResponseListGuests>([]);
@ -233,8 +252,8 @@ export const PartyUI: React.FC<{ party: ResponseCreateParty, adminToken: string,
<span>{confirmations[2]}</span> <span>{confirmations[2]}</span>
</div></div> </div></div>
{ hasExtras ? { hasExtras ?
<div className="header"><span className="lg">Extras</span><div className="extraTable heading" style={{"--num-columns": numExtraColumns} as React.CSSProperties}>{Object.keys(party.allowed_extra).map((k,i) => <div key={i}>{k}</div>)}</div></div> <div className="header"><span className="lg">Extras</span><div className="extraTable heading" style={{"--num-columns": numExtraColumns} as React.CSSProperties}>{Object.keys(party.allowed_extra).map((k,i) => <div key={i}>{k}</div>)}</div></div>
:null :null
} }
{guests.map((guest, index) => <Fragment key={index}> {guests.map((guest, index) => <Fragment key={index}>
<div className="guestname" onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}>{guest.token}</div> <div className="guestname" onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}>{guest.token}</div>
@ -242,8 +261,8 @@ export const PartyUI: React.FC<{ party: ResponseCreateParty, adminToken: string,
<div onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}>{guest.grammatical_gender}</div> <div onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}>{guest.grammatical_gender}</div>
<div onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}>{guest.coming ?? "?"}</div> <div onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}>{guest.coming ?? "?"}</div>
{hasExtras ? {hasExtras ?
<div ><ExtraTable party={party} guest={guest} onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}/></div> <div onContextMenu={(e) => { e.preventDefault(); exportPartyLink(guest); }} onClick={() => editUser(guest)}><ExtraTable party={party} guest={guest} /></div>
: null} : null}
</Fragment> </Fragment>
)} )}
</div> </div>
@ -261,10 +280,10 @@ export const PartyUI: React.FC<{ party: ResponseCreateParty, adminToken: string,
}; };
export const ExtraTable : React.FC<{guest: Person, party: ResponseCreateParty} & React.HTMLProps<HTMLDivElement>> = ({guest, party}) => { export const ExtraTable : React.FC<{guest: Person, party: ResponseCreateParty} & React.HTMLProps<HTMLDivElement>> = ({guest, party}) => {
const numExtraColumns = Object.keys(party.allowed_extra).length; const numExtraColumns = Object.keys(party.allowed_extra).length;
return <div className="extraTable" style={{"--num-columns": numExtraColumns} as React.CSSProperties}> return <div className="extraTable" style={{"--num-columns": numExtraColumns} as React.CSSProperties}>
{Object.keys(party.allowed_extra).map((k, i) => <div key={i}> {Object.keys(party.allowed_extra).map((k, i) => <div key={i}>
{guest.extra[k]?? ""} {guest.extra[k]?? ""}
</div>)} </div>)}
</div> </div>
} }