added search, unified controls and input elements, rendering improved
This commit is contained in:
parent
a0adef9ec5
commit
32e6018415
30231
package-lock.json
generated
30231
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -18,20 +18,14 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-preact-pure": "^2.0.0",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-preact": "^1.1.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-preset-preact": "^1.0.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"preact-cli": "^3.3.5",
|
||||
"sass-loader": "^10.2.1",
|
||||
"sirv-cli": "1.0.3"
|
||||
"preact-render-to-string": "^5.1.4",
|
||||
"sass-loader": "^10.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"preact": "^10.3.2",
|
||||
"preact-render-to-string": "^5.1.4",
|
||||
"preact-router": "^3.2.1",
|
||||
"sass": "^1.49.9"
|
||||
},
|
||||
|
@ -1,11 +1,30 @@
|
||||
const server = 'http://192.168.4.22'
|
||||
|
||||
async function restoreSession(reducer=null) {
|
||||
|
||||
let token = localStorage.getItem('token');
|
||||
let username = localStorage.getItem('username');
|
||||
console.log({type:'start', token, username});
|
||||
let active = token!==null && username!==null && await checkAuth(token);
|
||||
if(active&&reducer!==null){
|
||||
reducer({type:'start', token, username});
|
||||
console.log({type:'start', token, username});
|
||||
}
|
||||
return (active ? { active: true, token, username } : {active:false});
|
||||
}
|
||||
async function storeSession(username, token) {
|
||||
token = await token;
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('username', username);
|
||||
return token
|
||||
}
|
||||
|
||||
async function login(username, password) {
|
||||
let formData = new FormData();
|
||||
formData.append('username', username)
|
||||
formData.append('password', password);
|
||||
formData.append('action', 'login');
|
||||
const resp = fetch(`${server}/api/auth`, { method: 'POST', mode: 'cors', body: formData }).then(resp => resp.text());
|
||||
const resp = fetch(`${server}/api/auth`, { method: 'POST', mode: 'cors', body: formData }).then(resp => storeSession(username, resp.text()));
|
||||
return resp;
|
||||
}
|
||||
async function logout(token) {
|
||||
@ -13,10 +32,15 @@ async function logout(token) {
|
||||
formData.append('token', token);
|
||||
formData.append('action', 'logout');
|
||||
const resp = fetch(`${server}/api/auth`, { method: 'POST', mode: 'cors', body: formData }).then(resp => resp.text());
|
||||
localStorage.clear();
|
||||
return resp;
|
||||
}
|
||||
function checkAuth(token) {
|
||||
return token === 'DUMMYTOKEN';
|
||||
async function checkAuth(token) {
|
||||
let formData = new FormData()
|
||||
formData.append('token', token);
|
||||
formData.append('action', 'check');
|
||||
const resp = await fetch(`${server}/api/auth`, { method: 'POST', mode: 'cors', body: formData }).then(resp => resp.text()).then(text => text === 'valid');
|
||||
return await resp;
|
||||
}
|
||||
async function fetchdb(token = 'azif7eqCl5') {
|
||||
//fetch(`${server}/api/userdb`).then()
|
||||
@ -65,6 +89,6 @@ function parsedb(raw) {
|
||||
return users;
|
||||
|
||||
}
|
||||
const publicfunctions = { login, logout, checkAuth, parsedb,fetchdb, createCsvTable, updateUser, deleteUser, createUser };
|
||||
const publicfunctions = { login, logout, checkAuth, parsedb, fetchdb, createCsvTable, updateUser, deleteUser, createUser,restoreSession };
|
||||
export default { ...publicfunctions }
|
||||
export { login, logout, checkAuth, parsedb,fetchdb, createCsvTable, updateUser, deleteUser, createUser }
|
||||
export { login, logout, checkAuth, parsedb, fetchdb, createCsvTable, updateUser, deleteUser, createUser, restoreSession }
|
@ -1,16 +1,29 @@
|
||||
import { h } from "preact";
|
||||
import { Router, route } from "preact-router";
|
||||
import { useReducer } from "preact/hooks";
|
||||
import { useEffect, useReducer } from "preact/hooks";
|
||||
import { Header, Menu } from "./index.js";
|
||||
import { Home, Users, EditUser, Login, Logout, System } from "../route";
|
||||
import { AppStateProvider, UserTableProvider, menuReducer, sessionReducer, userTableReducer } from "../store";
|
||||
|
||||
import api from "../api/index.js";
|
||||
function App() {
|
||||
// useReducer
|
||||
const menu = useReducer(menuReducer, false);
|
||||
const session = useReducer(sessionReducer, { active:true ,token: "0t6BF94Y92" });
|
||||
const session = useReducer(sessionReducer, {});
|
||||
const [usertable, userreducer] = useReducer(userTableReducer, []);
|
||||
console.log(session[0]);
|
||||
if(!session[0]||(session[0]&&session[0].active===undefined))
|
||||
api.restoreSession(session[1]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(session[0]&&session[0].active){
|
||||
console.log("triggerimport");
|
||||
api.fetchdb(session[0].token).then(imported => {
|
||||
let action = { type: 'import', imported };
|
||||
userreducer(action);
|
||||
});
|
||||
}},[session]);
|
||||
|
||||
console.log(session);
|
||||
this.menu_items = [
|
||||
{ text: "Übersicht", path: "/" },
|
||||
{ text: "Benutzer anlegen", path: "/newuser" },
|
||||
|
2
src/components/controls/index.js
Normal file
2
src/components/controls/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import {TextBox,CheckBox,Button} from './input'
|
||||
export {TextBox,CheckBox,Button}
|
22
src/components/controls/input.jsx
Normal file
22
src/components/controls/input.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { h } from 'preact'
|
||||
|
||||
function TextBox({ id, type = 'text', label, disabled, formdata, formchange, maxlength }) {
|
||||
let onChange = (e) => formchange({ ...formdata, [e.target.id]: e.target.value });
|
||||
return (<div className={'textbox'} >
|
||||
<input placeholder=" " disabled={disabled} type={type} value={formdata[id]?formdata[id]:''} id={id} onInput={onChange} maxlength={maxlength} name={`form-${id}`} />
|
||||
<label for={id}>{label}</label>
|
||||
</div>)
|
||||
}
|
||||
function CheckBox({ id, disabled, label, formdata, formchange }) {
|
||||
let onChange = (e) => formchange({ ...formdata, [e.target.id]: e.target.checked });
|
||||
return (<div className={'checkbox'} >
|
||||
<input disabled={disabled} type={'checkbox'} checked={formdata[id] ? 'on' : ''} id={id} onChange={onChange} name={`from-${id}`} />
|
||||
<label for={id}>{label}</label>
|
||||
</div>)
|
||||
}
|
||||
function Button({ onClick, children, type }) {
|
||||
return (<div className={'button'} onClick={onClick} >
|
||||
<button type={type} >{children}</button>
|
||||
</div>)
|
||||
}
|
||||
export { TextBox, CheckBox, Button }
|
@ -24,9 +24,9 @@ function UserList({userlist,editUser, deleteUser, start, end}) {
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
{userlist&&(start!==undefined&&end?userlist.slice(start,end).map((user)=>displayUser(user)):userlist.map((user)=>displayUser(user)))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -3,54 +3,108 @@ import { useContext, useEffect, useState } from 'preact/hooks';
|
||||
import { route } from 'preact-router';
|
||||
import AppState, { UserTable } from "../../store";
|
||||
import api from '../../api'
|
||||
import { Breadcrumbs } from "../../components";
|
||||
import { CheckBox, Button, TextBox } from '../../components/controls';
|
||||
function EditUser({ userid }) {
|
||||
let [sessiondata,] = useContext(AppState).session;
|
||||
const { usertable, userreducer } = useContext(UserTable);
|
||||
|
||||
const [formdata, formchange] = useState({});
|
||||
const onChange = (e) => {
|
||||
console.log(e);
|
||||
if (e.target.type === 'checkbox')
|
||||
formchange({ ...formdata, [e.target.id]: e.target.checked })
|
||||
else
|
||||
formchange({ ...formdata, [e.target.id]: e.target.value })
|
||||
}
|
||||
useEffect(()=>{
|
||||
let user = userid !== undefined?usertable.find(u => u.uid === userid):undefined;
|
||||
if(user!==undefined){
|
||||
let maxlength_uid = 10;
|
||||
let maxlength_name = 25;
|
||||
let maxlength_pin = 10;
|
||||
let maxlength_rfid = 8;
|
||||
useEffect(() => {
|
||||
let user = userid !== undefined ? usertable.find(u => u.uid === userid) : undefined;
|
||||
if (user !== undefined) {
|
||||
formchange(user);
|
||||
}
|
||||
},[userid]);
|
||||
}, [userid, usertable]);
|
||||
function uidCount(state, user) {
|
||||
return state.reduce((pre, curr) => pre + curr.uid == user.uid ? 1 : 0, 0);
|
||||
}
|
||||
const onReset = (e) => {
|
||||
e.preventDefault();
|
||||
let user = userid !== undefined ? usertable.find(u => u.uid === userid) : undefined;
|
||||
formchange(user !== undefined ? user : {});
|
||||
}
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
console.log(api);
|
||||
if(formdata.line!==undefined){
|
||||
let idcount = uidCount(usertable, formdata);
|
||||
console.log(idcount);
|
||||
if (formdata.line !== undefined) {
|
||||
api.updateUser(sessiondata.token, formdata).then(r => {
|
||||
userreducer({ type: 'update', user: r })
|
||||
});
|
||||
}else{
|
||||
route('/users');
|
||||
} else if (idcount === 0) {
|
||||
api.createUser(sessiondata.token, formdata).then(r => {
|
||||
userreducer({ type: 'create', user: r })
|
||||
});
|
||||
}
|
||||
|
||||
route('/users');
|
||||
}
|
||||
const Input = ({ id, type = 'text', label, disabled }) => (<div className={'textbox'} ><input placeholder=" " disabled={disabled} type={type} checked={type == 'checkbox' && formdata[id] ? 'on' : ''} value={formdata[id]} id={id} onChange={onChange} name={`edit-${id}`} /><label for={id}>{label}</label></div>)
|
||||
else {
|
||||
alert(`UID ${formdata.uid} wurde bereits ${idcount} mal benutzt`);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className='container'>
|
||||
<h1>{formdata.line != undefined ? "Benutzer Bearbeiten" : "Neuer Benutzer"}</h1>
|
||||
<Breadcrumbs items={['Benutzerverwaltung',formdata.line != undefined ? "Benutzer Bearbeiten" : "Neuer Benutzer"]} />
|
||||
<div className={'contentbox'} >
|
||||
<h2>{formdata.line != undefined ? "Benutzer Bearbeiten" : "Neuer Benutzer"}</h2>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Input id="uid" label="UserID" />
|
||||
<Input id="first_name" label="Vorname" />
|
||||
<Input id="last_name" label="NachName" />
|
||||
<Input id="rfid_uid" label="RFID" />
|
||||
<Input id="user_pin" label="Pin" />
|
||||
<Input id="enabled" type={'checkbox'} label="" />
|
||||
<div className={'button'} ><input type={'submit'}>Speichern</input> </div>
|
||||
<h3>Eindeutige Identifikationsnummer</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={formchange} id="uid" label="Benutzer-ID" maxlength={maxlength_uid} />
|
||||
{<span class="textbox__info"> {formdata["uid"]?formdata["uid"].length:0} von {maxlength_uid} Zeichen verwendet.</span>}
|
||||
</div>
|
||||
<div className='column'>
|
||||
<b>Info</b>
|
||||
<span> Nummer muss einmalig sein</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Persönliche Daten</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="first_name" label="Vorname" maxlength={maxlength_name} />
|
||||
{<span class="textbox__info"> {formdata["first_name"] ?formdata["first_name"].length:0} von {maxlength_name} Zeichen verwendet.</span>}
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="last_name" label="NachName" maxlength={maxlength_name} />
|
||||
{<span class="textbox__info"> {formdata["last_name"]?formdata["last_name"].length:0} von {maxlength_name} Zeichen verwendet.</span>}
|
||||
</div>
|
||||
</div>
|
||||
<h3>Authentifizierung</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="rfid_uid" label="RFID" maxlength={maxlength_rfid} />
|
||||
{<span class="textbox__info"> {formdata["rfid_uid"]?formdata["rfid_uid"].length:0} von {maxlength_rfid} Zeichen verwendet.</span>}
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="user_pin" label="Pin" maxlength={maxlength_pin} />
|
||||
{<span class="textbox__info"> {formdata["user_pin"] ?formdata["user_pin"].length:0} von {maxlength_pin} Zeichen verwendet.</span>}
|
||||
</div>
|
||||
</div>
|
||||
<h3>Status</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<CheckBox formdata={formdata} formchange={formchange} id="enabled" type={'checkbox'} label="Benutzer darf sich anmelden" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className='row' style={'justify-content: space-between'}>
|
||||
<div className='column'>
|
||||
<Button onClick={onSubmit} >Speichern</Button>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Button onClick={onReset} >Zurücksetzen</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default EditUser;
|
@ -1,12 +1,13 @@
|
||||
import { h } from 'preact';
|
||||
import { Link } from 'preact-router';
|
||||
import { useContext, useState } from 'preact/hooks';
|
||||
import { useContext } from 'preact/hooks';
|
||||
import AppState from '../../store/AppState';
|
||||
|
||||
import { Breadcrumbs } from "../../components";
|
||||
function Home() {
|
||||
let [sessiondata, ] = useContext(AppState).session;
|
||||
return (
|
||||
<div class="container">
|
||||
<Breadcrumbs items={['Startseite']} />
|
||||
<div className={'contentbox'} >
|
||||
<h2>Startseite</h2>
|
||||
<p>Willkommen zurück {sessiondata.username} </p>
|
||||
@ -18,7 +19,6 @@ function Home() {
|
||||
</div>
|
||||
<div>
|
||||
<p>Hier können Sie den Türöffner manuell aktivieren.</p>
|
||||
|
||||
<p>Um Benutzer anzulegen, öffnen sie das Menü und tippen auf <Link href="/newuser">Benutzer anlegen</Link> </p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@ import { route } from 'preact-router';
|
||||
import { useContext, useState } from 'preact/hooks';
|
||||
import AppState from '../../store/AppState';
|
||||
import Breadcrumbs from '../../components/breadcrumbs';
|
||||
import { CheckBox, Button, TextBox } from '../../components/controls';
|
||||
import api from '../../api'
|
||||
function Login() {
|
||||
let [sessiondata, setsession] = useContext(AppState).session;
|
||||
@ -22,7 +23,7 @@ function Login() {
|
||||
setsession(newsession);
|
||||
}
|
||||
else {
|
||||
set({ ...val, error: "user" });
|
||||
set(prev=>({ ...prev, error: "user" }));
|
||||
}
|
||||
set({ username: '', password: '' });
|
||||
})
|
||||
@ -36,17 +37,18 @@ function Login() {
|
||||
<p >Bitte melden Sie sich mit ihren Nutzerdaten an.</p>
|
||||
{val.error !== null && <span style={'color: red'}>Fehler: Ungültige Anmeldedaten.</span>}
|
||||
<form id="login_form" onSubmit={onSubmit} >
|
||||
|
||||
|
||||
<div class="input-box">
|
||||
<input id="name" type="text" placeholder="Username" onInput={e => set({ ...val, username: e.target.value })} value={val.username} />
|
||||
<label for="name">Benutzername</label>
|
||||
<div className='row'>
|
||||
<TextBox maxlength={25} formdata={val} formchange={set} id="username" label="Benutzername" />
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<input id="pass" type="password" placeholder="Passwort" onInput={e => set({ ...val, password: e.target.value })} value={val.password} />
|
||||
<label for="pass">Password</label>
|
||||
<div className='row'>
|
||||
<TextBox maxlength={25} formdata={val} formchange={set} id="password" label="Passwort" type='password' />
|
||||
|
||||
</div>
|
||||
<div class={'button'} onClick={onSubmit} ><input type="submit" value="Anmelden" /></div>
|
||||
<div className='row'>
|
||||
<CheckBox id="permanent" formdata={val} formchange={set} label="Angemeldet bleiben?" />
|
||||
|
||||
</div>
|
||||
<Button onClick={onSubmit}>Anmelden</Button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { h } from 'preact';
|
||||
|
||||
import { Breadcrumbs } from "../../components";
|
||||
|
||||
function System() {
|
||||
|
||||
return (
|
||||
<div className='container'>
|
||||
<Breadcrumbs items={['Systemeinstellungen']} />
|
||||
<div className={'contentbox'} >
|
||||
<h2>System</h2>
|
||||
<h3>WiFi Setup</h3>
|
||||
|
@ -2,48 +2,64 @@ import { h } from "preact";
|
||||
import { UserList, Pageselector, Breadcrumbs } from "../../components";
|
||||
import api from '../../api'
|
||||
import { route } from 'preact-router';
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useContext, useEffect, useState, useMemo } from "preact/hooks";
|
||||
import AppState, { UserTable } from "../../store";
|
||||
function Users({ pageid }) {
|
||||
const [viewstate, setview] = useState({ limit: 100, page: 1, pages: null })
|
||||
const { usertable, userreducer } = useContext(UserTable);
|
||||
let [sessiondata,] = useContext(AppState).session;
|
||||
const [viewstate, setview] = useState({ limit: 100, page: 1, pages: 0, query: '' });
|
||||
const matchUser = (user, query, fields = ['uid', 'first_name', 'last_name'], exact = false) => {
|
||||
let words = query.toLowerCase().split(' ');
|
||||
let matches = 0;
|
||||
|
||||
for (let word of words) {
|
||||
let match = false;
|
||||
for (let key of fields){
|
||||
let cmp = user[key].toLowerCase();
|
||||
if (exact ? cmp == word : cmp.includes(word)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
matches += 1;
|
||||
if(matches >= words.length) return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
const results = useMemo(() => (viewstate.query !== '') ? usertable.filter(u => matchUser(u, viewstate.query)) : usertable, [viewstate.query, usertable]);
|
||||
|
||||
|
||||
|
||||
|
||||
const setPage = (e) => {
|
||||
e.preventDefault();
|
||||
console.log(e)
|
||||
let page = e.target.text;
|
||||
setview({ ...viewstate, page })
|
||||
setview(state => ({ ...state, page }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (usertable === undefined)
|
||||
console.log("TOKEN");
|
||||
console.log(sessiondata.token);
|
||||
api.fetchdb(sessiondata.token).then(imported => {
|
||||
let action = { type: 'import', imported };
|
||||
userreducer(action);
|
||||
});
|
||||
}, [sessiondata]);
|
||||
|
||||
useEffect(() => {
|
||||
setview({ ...viewstate, pages: Math.ceil(usertable.length / viewstate.limit) });
|
||||
console.log("New View effect")
|
||||
}, [usertable])
|
||||
setview((state) => {
|
||||
let pages = Math.ceil(results.length / state.limit)
|
||||
if (state.page >= pages) return ({ ...state, pages, page: pages })
|
||||
return ({ ...state, pages })
|
||||
})
|
||||
}, [results.length, viewstate.limit])
|
||||
useEffect(() => {
|
||||
if (pageid && !isNaN(pageid))
|
||||
setview({ ...viewstate, page: pageid })
|
||||
setview((state) => ({ ...state, page: pageid }))
|
||||
}, [pageid])
|
||||
|
||||
const deleteUser = (user) => {
|
||||
api.deleteUser(sessiondata.token,user).then(r=>{
|
||||
api.deleteUser(sessiondata.token, user).then(r => {
|
||||
let action = {
|
||||
type: 'delete',
|
||||
user, r
|
||||
}
|
||||
userreducer(action)
|
||||
})
|
||||
|
||||
}
|
||||
const calculateView = () => {
|
||||
let start = viewstate.limit * (viewstate.page - 1);
|
||||
@ -53,15 +69,22 @@ function Users({ pageid }) {
|
||||
const editUser = (user) => {
|
||||
route(`/edituser/${user.uid}`);
|
||||
}
|
||||
const navigation = ["Users"];
|
||||
const setLimit = (e) => {
|
||||
setview(state => ({ ...state, limit: e.target.value }));
|
||||
console.log(e.target.value);
|
||||
console.log(viewstate);
|
||||
}
|
||||
const navigation = ["Benutzerverwaltung",'Übersicht'];
|
||||
//
|
||||
return (
|
||||
<div class="container">
|
||||
<Breadcrumbs items={navigation} />
|
||||
<div className={'contentbox'} >
|
||||
<div>Suche: <input type={'text'} /><button>Hinzufügen</button> Limit: <select><optgroup label={'Anzahl'}><option>10</option><option>25</option><option>50</option><option>100</option></optgroup><option>Alle</option></select></div>
|
||||
<div>Suche: <input type={'text'} onInput={(e) => setview(state => ({ ...state, query: e.target.value }))} value={viewstate.query} />
|
||||
<button>Hinzufügen</button>
|
||||
Limit: <select onChange={setLimit} value={viewstate.limit}><optgroup label={'Anzahl'}><option>10</option><option>25</option><option>50</option><option>100</option><option>200</option></optgroup><option>Alle</option></select></div>
|
||||
</div>
|
||||
<UserList {...calculateView()} userlist={usertable} deleteUser={deleteUser} editUser={editUser} />
|
||||
<UserList {...calculateView()} userlist={results} deleteUser={deleteUser} editUser={editUser} />
|
||||
<Pageselector start={1} end={viewstate.pages} current={viewstate.page} setPage={setPage} />
|
||||
|
||||
|
||||
|
@ -18,12 +18,12 @@ export const sessionReducer = (state, action) => {
|
||||
export const userTableReducer = (state, action) => {
|
||||
let user = action.user;
|
||||
switch (action.type) {
|
||||
case 'create': return [...state, { line: state.length, ...user }]
|
||||
case 'create': return [...state, { line: state.length, ...user }];
|
||||
case 'delete': {
|
||||
let newstate = [];
|
||||
let newindex = 0;
|
||||
state.forEach((u, i) => {
|
||||
if (user.uid && u.uid != user.uid|| user.line && i != user.line )
|
||||
if (user.uid && u.uid != user.uid || user.line && i != user.line)
|
||||
newstate.push({ ...u, line: newindex++ })
|
||||
return newstate;
|
||||
}, []);
|
||||
|
@ -10,7 +10,7 @@
|
||||
text-decoration: none
|
||||
border: solid 1px #999
|
||||
border-radius: .2rem
|
||||
input[type=submit]
|
||||
button
|
||||
display: block
|
||||
background: none
|
||||
width: 100%
|
||||
@ -32,7 +32,7 @@
|
||||
transition: all ease-in-out 250ms
|
||||
&:hover
|
||||
color: #fff
|
||||
input[type=submit]
|
||||
button
|
||||
background: #333
|
||||
color: #fff
|
||||
border-radius: .3rem
|
||||
|
@ -1,7 +1,9 @@
|
||||
@mixin textfield
|
||||
margin: 0 1em
|
||||
width: 100%
|
||||
position: relative
|
||||
display: inline-block
|
||||
margin: 0 .25em 1em .25em
|
||||
display: block
|
||||
margin: 0
|
||||
background: #fafafa
|
||||
border-radius: .3em
|
||||
border-bottom: 1px solid #ccc
|
||||
@ -35,3 +37,51 @@
|
||||
top: 0em
|
||||
left: 0em
|
||||
font-size: 1em
|
||||
&__info
|
||||
color: #ccc
|
||||
font-size: 0.7em
|
||||
|
||||
@mixin checkbox
|
||||
position: relative
|
||||
font-size: 1.5em
|
||||
label
|
||||
user-select: none
|
||||
margin-left: 1em
|
||||
padding-left: .5em
|
||||
transition: all 250ms ease-in-out
|
||||
&::before
|
||||
position: absolute
|
||||
content: ''
|
||||
left: 0
|
||||
top: 0
|
||||
width: 1em
|
||||
height: 1em
|
||||
background: radial-gradient(circle, #e2e2e2 0%, #b8b8b8 100%)
|
||||
border-radius: .3em
|
||||
border: solid 1px #ccc
|
||||
box-shadow: 0 0 .5em #fff, inset 0 0 .3em #fff
|
||||
|
||||
&::after
|
||||
content: ''
|
||||
left: 0
|
||||
top: 0
|
||||
width: 1em
|
||||
height: 1em
|
||||
position: absolute
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
transform: scale(0)
|
||||
padding: 0
|
||||
text-shadow: 0 0 .2em #888
|
||||
transition: all 250ms ease-in-out
|
||||
input
|
||||
display: none
|
||||
&:checked + label
|
||||
text-shadow: 0 0 .5em #888
|
||||
&::before
|
||||
box-shadow: 0 0 .5em #fff, inset 0 0 .5em #fff
|
||||
&::after
|
||||
transform: scale(1)
|
||||
content: '✔'
|
||||
color: #fff
|
||||
|
@ -74,23 +74,30 @@ body
|
||||
.page-nav-bar
|
||||
@include pageselector.pageselector
|
||||
.user-list-item
|
||||
position: relative
|
||||
overflow: hidden
|
||||
display: flex
|
||||
display: inline-flex
|
||||
flex-shrink: 1
|
||||
min-width: calc(50% - 1em)
|
||||
flex-grow: 1
|
||||
flex-direction: row
|
||||
background: #eee
|
||||
margin: 0 0.5em
|
||||
padding: .5em 0
|
||||
padding: .5em 3em 0 0
|
||||
border-radius: .3em
|
||||
&:not(:last-child)
|
||||
margin-bottom: .5em
|
||||
.user-attributes
|
||||
padding: .5em
|
||||
.btn-group
|
||||
margin-left: auto
|
||||
.btn-group
|
||||
position: absolute
|
||||
top: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
display: inline-flex
|
||||
flex-direction: column
|
||||
align-self: flex-end
|
||||
justify-self: flex-end
|
||||
align-items: center
|
||||
justify-content: center
|
||||
@mixin button()
|
||||
position: relative
|
||||
border: none
|
||||
@ -161,3 +168,21 @@ footer
|
||||
.textbox
|
||||
@include input.textfield
|
||||
font-size: 1.5em
|
||||
.checkbox
|
||||
@include input.checkbox
|
||||
|
||||
.row
|
||||
display: flex
|
||||
justify-content: space-evenly
|
||||
align-items: stretch
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
width: 100%
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1em
|
||||
.column
|
||||
padding: .5em
|
||||
flex-grow: 1
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-wrap: wrap
|
Loading…
x
Reference in New Issue
Block a user