mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-21 20:44:18 +00:00
🔧 New dashboard layout
This commit is contained in:
parent
ab919f351b
commit
a506a07588
14 changed files with 249 additions and 87 deletions
|
@ -112,6 +112,8 @@ export default {
|
|||
postUniq: "Totale utenti che hanno postato",
|
||||
postPop: "Utente più attivo",
|
||||
filters: "Filtri",
|
||||
|
||||
errorMissingFields: "Errore: Uno o più campi richiesti non sono stati compilati."
|
||||
},
|
||||
// 🇬🇧
|
||||
en: {
|
||||
|
|
|
@ -14,6 +14,9 @@ import PageShare from "./routes/PageShare"
|
|||
export default function PageSwitcher({ ...props }) {
|
||||
return (
|
||||
<Switch {...props}>
|
||||
<Route path={"/repositories/create"} exact={true}>
|
||||
<PageRepositoryCreate/>
|
||||
</Route>
|
||||
<Route path={"/repositories/:id/alerts"} exact={true}>
|
||||
<PageRepositoryAlerts/>
|
||||
</Route>
|
||||
|
@ -35,9 +38,6 @@ export default function PageSwitcher({ ...props }) {
|
|||
<Route path={"/settings"} exact={true}>
|
||||
<PageSettings/>
|
||||
</Route>
|
||||
<Route path={"/dashboard"} exact={true}>
|
||||
<PageRepositoryCreate/>
|
||||
</Route>
|
||||
<Route path={"/"}>
|
||||
<PageLogin/>
|
||||
</Route>
|
||||
|
|
|
@ -17,7 +17,6 @@ import ContextUser from "../../contexts/ContextUser"
|
|||
* @param destroy - Function with a single "id" parameter to call when the delete repository button is clicked.
|
||||
* @param firstLoad - If the repositories are loading and a loading message should be displayed.
|
||||
* @param running - If an action is running on the viewset.
|
||||
* @param className - Additional class(es) to append to the box.
|
||||
* @param props - Additional props to pass to the box.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
|
@ -33,7 +32,6 @@ export default function BoxRepositories(
|
|||
destroy,
|
||||
loading,
|
||||
running,
|
||||
className,
|
||||
...props
|
||||
})
|
||||
{
|
||||
|
|
|
@ -73,7 +73,7 @@ export default function BoxRepositoryCreate({ running, ...props }) {
|
|||
</FormLabel>
|
||||
{error ?
|
||||
<FormAlert color={"Red"}>
|
||||
{error.toString()}
|
||||
{strings[error.data.code]}
|
||||
</FormAlert>
|
||||
: null}
|
||||
{id ?
|
||||
|
@ -91,7 +91,7 @@ export default function BoxRepositoryCreate({ running, ...props }) {
|
|||
style={{ "gridColumn": "2" }}
|
||||
icon={faPencilAlt}
|
||||
color={"Green"}
|
||||
onClick={_ => goToOnSuccess(save, history, "/repositories")()}
|
||||
onClick={save}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.save}
|
||||
|
@ -102,7 +102,7 @@ export default function BoxRepositoryCreate({ running, ...props }) {
|
|||
style={{ "gridColumn": "1 / 3" }}
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={_ => goToOnSuccess(save, history, "/repositories")()}
|
||||
onClick={save}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.createRepo}
|
||||
|
|
|
@ -11,6 +11,10 @@ import BoxRepositoryCreate from "../interactive/BoxRepositoryCreate"
|
|||
import classNames from "classnames"
|
||||
import ContextUser from "../../contexts/ContextUser"
|
||||
import useBackend from "../../hooks/useBackend"
|
||||
import { Condition } from "../../objects/Condition"
|
||||
import useBackendResource from "../../hooks/useBackendResource"
|
||||
import useBackendViewset from "../../hooks/useBackendViewset"
|
||||
import { Redirect } from "react-router"
|
||||
|
||||
|
||||
export default function RepositoryEditor({
|
||||
|
@ -23,74 +27,84 @@ export default function RepositoryEditor({
|
|||
evaluation_mode: evaluationMode,
|
||||
className,
|
||||
}) {
|
||||
/** The currently logged in user. */
|
||||
const { user } = useContext(ContextUser)
|
||||
|
||||
/** The repository name. */
|
||||
const [_name, setName] = useState(name ?? "")
|
||||
|
||||
/** The repository state (active / archived). */
|
||||
const [_isActive, setActive] = useState(isActive ?? true)
|
||||
|
||||
/** The start date of the data gathering. */
|
||||
const [_start, setStart] = useState(start ?? new Date().toISOString())
|
||||
|
||||
/** The end date of the data gathering. */
|
||||
const [_end, setEnd] = useState(end ?? new Date().toISOString())
|
||||
|
||||
/** The conditions of the data gathering. */
|
||||
const {
|
||||
value: _conditions,
|
||||
value: rawConditions,
|
||||
setValue: setRawConditions,
|
||||
appendValue: appendRawCondition,
|
||||
removeValue: removeRawCondition,
|
||||
spliceValue: spliceRawCondition,
|
||||
} = useArrayState(conditions)
|
||||
const _conditions = rawConditions.map(cond => Condition.fromRaw(cond))
|
||||
|
||||
/** The operator the conditions should be evaluated with. */
|
||||
const [_evaluationMode, setEvaluationMode] = useState(evaluationMode ?? 0)
|
||||
|
||||
const { user, fetchDataAuth } = useContext(ContextUser)
|
||||
|
||||
const method = id ? "PUT" : "POST"
|
||||
const path = id ? `/api/v1/repositories/${id}` : `/api/v1/repositories/`
|
||||
const body = useMemo(
|
||||
() => {
|
||||
return {
|
||||
"conditions": _conditions,
|
||||
"end": null,
|
||||
"evaluation_mode": _evaluationMode,
|
||||
"id": id,
|
||||
"is_active": true,
|
||||
"name": _name,
|
||||
"owner": user,
|
||||
"start": null,
|
||||
}
|
||||
},
|
||||
[_conditions, _evaluationMode, id, _name, user],
|
||||
/** The backend viewset to use to create / edit the repository. */
|
||||
const {running, error, createResource, editResource} = useBackendViewset(
|
||||
`/api/v1/repositories/`,
|
||||
"id",
|
||||
{
|
||||
list: false,
|
||||
create: true,
|
||||
retrieve: false,
|
||||
edit: true,
|
||||
destroy: false,
|
||||
command: false,
|
||||
action: false,
|
||||
}
|
||||
)
|
||||
const { error, loading, fetchNow } = useBackend(fetchDataAuth, method, path, body)
|
||||
|
||||
/** If `true`, switches to the repository page on the next render. */
|
||||
const [switchPage, setSwitchPage] = useState(false)
|
||||
|
||||
/**
|
||||
* Save the current changes, creating or editing it as needed.
|
||||
*
|
||||
* @type {(function(): Promise<void>)|*}
|
||||
*/
|
||||
const save = useCallback(
|
||||
async () => {
|
||||
const body = {
|
||||
"id": id,
|
||||
"name": _name,
|
||||
"start": null,
|
||||
"is_active": true,
|
||||
"end": null,
|
||||
"owner": user,
|
||||
"spectators": null,
|
||||
"evaluation_mode": _evaluationMode,
|
||||
"conditions": _conditions,
|
||||
}
|
||||
|
||||
if(!id) {
|
||||
console.info("Creando una nuova repository avente come corpo: ", body)
|
||||
console.info("Creating new repository with body: ", body)
|
||||
await createResource(body)
|
||||
}
|
||||
else {
|
||||
console.info("Modificando la repository ", id, " con corpo: ", body)
|
||||
console.info("Editing repository ", id, " with body: ", body)
|
||||
await editResource(id, body)
|
||||
}
|
||||
await fetchNow()
|
||||
setSwitchPage(true)
|
||||
},
|
||||
[id, body, fetchNow],
|
||||
[id, createResource, editResource, _conditions, _evaluationMode, _name, user],
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Cancel the changes made so far to the repository.
|
||||
*
|
||||
* @type {(function(): void)|*}
|
||||
*/
|
||||
const revert = useCallback(
|
||||
() => {
|
||||
setName(name)
|
||||
setActive(isActive)
|
||||
setStart(start)
|
||||
setEnd(end)
|
||||
setRawConditions(conditions)
|
||||
setEvaluationMode(evaluationMode)
|
||||
},
|
||||
|
@ -99,14 +113,11 @@ export default function RepositoryEditor({
|
|||
|
||||
/**
|
||||
* Try to add a new condition, logging a message to the console if something goes wrong.
|
||||
*
|
||||
* @type {(function(): void)|*}
|
||||
*/
|
||||
const addCondition = useCallback(
|
||||
(newCond) => {
|
||||
// Check for content
|
||||
if(!newCond.content) {
|
||||
console.debug("Impossibile aggiungere ", newCond, ": l'oggetto è vuoto.")
|
||||
return
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
let duplicate = null
|
||||
|
@ -117,27 +128,29 @@ export default function RepositoryEditor({
|
|||
}
|
||||
}
|
||||
if(duplicate) {
|
||||
console.debug("Impossibile aggiungere ", newCond, ": ", duplicate, " è già esistente.")
|
||||
console.debug("Cannot add ", newCond, ": ", duplicate, " already exists.")
|
||||
return
|
||||
}
|
||||
|
||||
console.debug("Aggiungendo ", newCond, " alle condizioni del repository")
|
||||
console.debug("Adding ", newCond, " to the repository conditions")
|
||||
appendRawCondition(newCond)
|
||||
},
|
||||
[_conditions, appendRawCondition],
|
||||
)
|
||||
|
||||
// Hack to switch page on success
|
||||
if(!error && switchPage) {
|
||||
return <Redirect to={"/repositories"}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextRepositoryEditor.Provider
|
||||
value={{
|
||||
id,
|
||||
name: _name, setName,
|
||||
isActive: _isActive, setActive,
|
||||
start: _start, setStart,
|
||||
end: _end, setEnd,
|
||||
conditions: _conditions, addCondition, appendRawCondition, removeRawCondition, spliceRawCondition,
|
||||
evaluationMode: _evaluationMode, setEvaluationMode,
|
||||
error, loading,
|
||||
error, running,
|
||||
revert, save,
|
||||
}}
|
||||
>
|
||||
|
@ -147,7 +160,7 @@ export default function RepositoryEditor({
|
|||
<BoxConditionUser className={Style.SearchByUser}/>
|
||||
<BoxConditionDatetime className={Style.SearchByTimePeriod}/>
|
||||
<BoxConditions className={Style.Conditions}/>
|
||||
<BoxRepositoryCreate running={loading} className={Style.CreateDialog}/>
|
||||
<BoxRepositoryCreate running={running} className={Style.CreateDialog}/>
|
||||
</div>
|
||||
</ContextRepositoryEditor.Provider>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useContext, useState } from "react"
|
||||
import ContextServer from "../contexts/ContextServer"
|
||||
import ContextUser from "../contexts/ContextUser"
|
||||
import { ServerNotConfiguredError, FetchAlreadyRunningError, DecodeError, ResultError } from "../objects/Errors"
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useEffect, useState } from "react"
|
||||
import useBackendRequest from "./useBackendRequest"
|
||||
import { ViewNotAllowedError } from "../objects/Errors"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -72,9 +73,10 @@ export default function useBackendResource(
|
|||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
throw e
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResource(refreshedResource)
|
||||
return refreshedResource
|
||||
},
|
||||
|
@ -89,9 +91,10 @@ export default function useBackendResource(
|
|||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
throw e
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResource(editedResource)
|
||||
return editedResource
|
||||
},
|
||||
|
@ -105,9 +108,10 @@ export default function useBackendResource(
|
|||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
throw e
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResource(null)
|
||||
return null
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useEffect, useState } from "react"
|
||||
import useBackendRequest from "./useBackendRequest"
|
||||
import { ViewNotAllowedError } from "../objects/Errors"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -98,60 +99,97 @@ export default function useBackendViewset(resourcesPath, pkName,
|
|||
|
||||
const listResources = useCallback(
|
||||
async () => {
|
||||
let res
|
||||
try {
|
||||
setResources(await apiList())
|
||||
res = await apiList()
|
||||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
throw e
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
return {}
|
||||
setResources(res)
|
||||
},
|
||||
[apiList],
|
||||
)
|
||||
|
||||
const retrieveResource = useCallback(
|
||||
async (pk) => {
|
||||
const refreshedResource = await apiRetrieve(pk)
|
||||
setResources(res => res.map(resource => {
|
||||
let res
|
||||
try {
|
||||
res = await apiRetrieve(pk)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => r.map(resource => {
|
||||
if(resource[pkName] === pk) {
|
||||
return refreshedResource
|
||||
return res
|
||||
}
|
||||
return resource
|
||||
}))
|
||||
return refreshedResource
|
||||
|
||||
return res
|
||||
},
|
||||
[apiRetrieve, pkName],
|
||||
)
|
||||
|
||||
const createResource = useCallback(
|
||||
async (data) => {
|
||||
const newResource = await apiCreate(data)
|
||||
setResources(res => [...res, newResource])
|
||||
return newResource
|
||||
let res
|
||||
try {
|
||||
res = await apiCreate(data)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => [...r, res])
|
||||
return res
|
||||
},
|
||||
[apiCreate],
|
||||
)
|
||||
|
||||
const editResource = useCallback(
|
||||
async (pk, data) => {
|
||||
const editedResource = await apiEdit(pk, data)
|
||||
setResources(res => res.map(resource => {
|
||||
let res
|
||||
try {
|
||||
res = await apiEdit(pk, data)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => r.map(resource => {
|
||||
if(resource[pkName] === pk) {
|
||||
return editedResource
|
||||
return res
|
||||
}
|
||||
return resource
|
||||
}))
|
||||
return editedResource
|
||||
return res
|
||||
},
|
||||
[apiEdit, pkName],
|
||||
)
|
||||
|
||||
const destroyResource = useCallback(
|
||||
async (pk) => {
|
||||
await apiDestroy(pk)
|
||||
setResources(res => res.filter(resource => resource[pkName] !== pk))
|
||||
try {
|
||||
await apiDestroy(pk)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => r.filter(resource => resource[pkName] !== pk))
|
||||
return null
|
||||
},
|
||||
[apiDestroy, pkName],
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
faQuestionCircle,
|
||||
IconDefinition,
|
||||
} from "@fortawesome/free-solid-svg-icons"
|
||||
import TimeRay from "./TimeRay"
|
||||
import MapArea from "./MapArea"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -24,6 +26,16 @@ export class Condition {
|
|||
this.id = id
|
||||
}
|
||||
|
||||
static fromRaw(data) {
|
||||
console.debug("Trying to serialize condition: ", data)
|
||||
|
||||
if(data.type === 0) return ConditionHashtag.fromRaw(data)
|
||||
else if(data.type === 2) return ConditionTime.fromRaw(data)
|
||||
else if(data.type === 3) return ConditionLocation.fromRaw(data)
|
||||
else if(data.type === 5) return ConditionUser.fromRaw(data)
|
||||
else return new Condition(data.type, data.content, data.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the condition as an object readable by the backend.
|
||||
*
|
||||
|
@ -61,6 +73,10 @@ export class ConditionHashtag extends Condition {
|
|||
super(0, hashtag, id)
|
||||
}
|
||||
|
||||
static fromRaw(data) {
|
||||
return new ConditionHashtag(data.content, data.id)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Grey",
|
||||
|
@ -80,6 +96,10 @@ export class ConditionUser extends Condition {
|
|||
super(5, user, id)
|
||||
}
|
||||
|
||||
static fromRaw(data) {
|
||||
return new ConditionUser(data.content, data.id)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Green",
|
||||
|
@ -102,6 +122,10 @@ export class ConditionTime extends Condition {
|
|||
this.timeRay = timeRay
|
||||
}
|
||||
|
||||
static fromRaw(data) {
|
||||
return new ConditionTime(TimeRay.fromRaw(data.content), data.id)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Yellow",
|
||||
|
@ -124,6 +148,10 @@ export class ConditionLocation extends Condition {
|
|||
this.mapArea = mapArea
|
||||
}
|
||||
|
||||
static fromRaw(data) {
|
||||
return new ConditionLocation(MapArea.fromRaw(data.content), data.id)
|
||||
}
|
||||
|
||||
display() {
|
||||
return {
|
||||
color: "Red",
|
||||
|
@ -133,3 +161,4 @@ export class ConditionLocation extends Condition {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Error thrown when a function is not implemented in the current class/instance.
|
||||
*/
|
||||
class NotImplementedError {
|
||||
export class NotImplementedError {
|
||||
name
|
||||
|
||||
constructor(name) {
|
||||
|
@ -13,7 +13,7 @@ class NotImplementedError {
|
|||
/**
|
||||
* An error in the N.E.S.T. frontend-backend communication.
|
||||
*/
|
||||
class BackendCommunicationError {
|
||||
export class BackendCommunicationError {
|
||||
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class BackendCommunicationError {
|
|||
/**
|
||||
* Error thrown when trying to access a backend view which doesn't exist or isn't allowed in the used hook.
|
||||
*/
|
||||
class ViewNotAllowedError extends BackendCommunicationError {
|
||||
export class ViewNotAllowedError extends BackendCommunicationError {
|
||||
view
|
||||
|
||||
constructor(view) {
|
||||
|
@ -35,7 +35,7 @@ class ViewNotAllowedError extends BackendCommunicationError {
|
|||
/**
|
||||
* Error thrown when trying to access a backend view when outside a {@link ContextServer}.
|
||||
*/
|
||||
class ServerNotConfiguredError extends BackendCommunicationError {
|
||||
export class ServerNotConfiguredError extends BackendCommunicationError {
|
||||
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class ServerNotConfiguredError extends BackendCommunicationError {
|
|||
*
|
||||
* This is not allowed due to potential race conditions.
|
||||
*/
|
||||
class FetchAlreadyRunningError extends BackendCommunicationError {
|
||||
export class FetchAlreadyRunningError extends BackendCommunicationError {
|
||||
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ class FetchAlreadyRunningError extends BackendCommunicationError {
|
|||
/**
|
||||
* Abstract class for {@link DecodeError} and {@link ResultError}.
|
||||
*/
|
||||
class FetchError extends BackendCommunicationError {
|
||||
export class FetchError extends BackendCommunicationError {
|
||||
status
|
||||
statusText
|
||||
|
||||
|
@ -69,7 +69,7 @@ class FetchError extends BackendCommunicationError {
|
|||
/**
|
||||
* Error thrown when the frontend can't parse the data received from the backend.
|
||||
*/
|
||||
class DecodeError extends FetchError {
|
||||
export class DecodeError extends FetchError {
|
||||
error
|
||||
|
||||
constructor(status, statusText, error) {
|
||||
|
@ -83,7 +83,7 @@ class DecodeError extends FetchError {
|
|||
/**
|
||||
* Error thrown when the backend returns a falsy `"result"` value.
|
||||
*/
|
||||
class ResultError extends FetchError {
|
||||
export class ResultError extends FetchError {
|
||||
status
|
||||
statusText
|
||||
data
|
||||
|
@ -102,3 +102,12 @@ class ResultError extends FetchError {
|
|||
return this.data.code
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SerializationError {
|
||||
invalidString
|
||||
|
||||
constructor(invalidString) {
|
||||
this.invalidString = invalidString
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import { getDistance } from "geolib"
|
||||
import osmZoomLevels from "../utils/osmZoomLevels"
|
||||
import Coordinates from "./Coordinates"
|
||||
import { SerializationError } from "./Errors"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -32,6 +34,21 @@ export default class MapArea {
|
|||
return new MapArea(osmZoomLevels[zoom], center)
|
||||
}
|
||||
|
||||
static rawRegex = /^< (?<radius>[0-9.]+) (?<lat>[0-9.]+) (?<lng>[0-9.]+)$/
|
||||
|
||||
static fromRaw(data) {
|
||||
const match = this.rawRegex.exec(data)
|
||||
|
||||
if(!match) throw new SerializationError(data)
|
||||
|
||||
const radius = Number(match.groups.radius)
|
||||
const lat = Number(match.groups.lat)
|
||||
const lng = Number(match.groups.lng)
|
||||
const center = new Coordinates(lat, lng)
|
||||
|
||||
return new MapArea(radius, center)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { SerializationError } from "./Errors"
|
||||
|
||||
|
||||
/**
|
||||
* An half-line of time, defined by a `date` and a boolean `isBefore` indicating if the time before or after the
|
||||
* specified date should be selected.
|
||||
|
@ -15,6 +18,18 @@ export default class TimeRay {
|
|||
this.date = date
|
||||
}
|
||||
|
||||
static rawRegex = /^(?<isBefore>[><]) (?<date>.+)$/
|
||||
|
||||
static fromRaw(data) {
|
||||
const match = this.rawRegex.exec(data)
|
||||
|
||||
if(!match) throw new SerializationError(data)
|
||||
|
||||
const isBefore = match.groups.isBefore === "<"
|
||||
const date = new Date(match.groups.date)
|
||||
return new TimeRay(isBefore, date)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,10 @@ import useBackendViewset from "../hooks/useBackendViewset"
|
|||
import BoxRepositories from "../components/interactive/BoxRepositories"
|
||||
import { useHistory } from "react-router"
|
||||
import ContextLanguage from "../contexts/ContextLanguage"
|
||||
import ContextUser from "../contexts/ContextUser"
|
||||
import BoxHeader from "../components/base/BoxHeader"
|
||||
import { faHome, faPlus } from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import Button from "../components/base/Button"
|
||||
|
||||
|
||||
export default function PageRepositoriesList({ children, className, ...props }) {
|
||||
|
@ -25,7 +28,20 @@ export default function PageRepositoriesList({ children, className, ...props })
|
|||
|
||||
return (
|
||||
<div className={classNames(Style.PageRepositories, className)} {...props}>
|
||||
<BoxHeader className={Style.Header}>
|
||||
<FontAwesomeIcon icon={faHome}/> {strings.dashboard}
|
||||
</BoxHeader>
|
||||
<div className={Style.Buttons}>
|
||||
<Button
|
||||
icon={faPlus}
|
||||
color={"Green"}
|
||||
onClick={() => history.push("/repositories/create")}
|
||||
>
|
||||
{strings.createRepo}
|
||||
</Button>
|
||||
</div>
|
||||
<BoxRepositories
|
||||
className={Style.ActiveRepositories}
|
||||
header={strings.menuActive}
|
||||
loading={!bv.firstLoad}
|
||||
running={bv.running}
|
||||
|
@ -37,6 +53,7 @@ export default function PageRepositoriesList({ children, className, ...props })
|
|||
edit={pk => history.push(`/repositories/${pk}/edit`)}
|
||||
/>
|
||||
<BoxRepositories
|
||||
className={Style.ArchivedRepositories}
|
||||
header={strings.menuArchived}
|
||||
loading={!bv.firstLoad}
|
||||
running={bv.running}
|
||||
|
|
|
@ -2,15 +2,34 @@
|
|||
display: grid;
|
||||
|
||||
grid-template-areas:
|
||||
"a"
|
||||
"b";
|
||||
|
||||
"h x"
|
||||
"a a"
|
||||
"b b";
|
||||
grid-template-columns: 4fr 1fr;
|
||||
grid-template-rows: auto 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Header {
|
||||
grid-area: h;
|
||||
}
|
||||
|
||||
.Buttons {
|
||||
grid-area: x;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.Buttons > * {
|
||||
box-shadow: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ActiveRepositories {
|
||||
grid-area: a;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue