@kaspernj/api-maker 1.0.216 → 1.0.219
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/base-model.mjs +1 -1
- package/src/bootstrap/attribute-row/basic-style.scss +9 -0
- package/src/bootstrap/attribute-row/index.jsx +84 -0
- package/src/bootstrap/attribute-rows.jsx +27 -0
- package/src/bootstrap/card.jsx +135 -0
- package/src/bootstrap/checkbox.jsx +79 -0
- package/src/bootstrap/checkboxes.jsx +122 -0
- package/src/bootstrap/index.js +0 -0
- package/src/bootstrap/input.jsx +160 -0
- package/src/bootstrap/invalid-feedback.jsx +31 -0
- package/src/bootstrap/live-table/model-row.jsx +150 -0
- package/src/bootstrap/live-table.jsx +399 -0
- package/src/bootstrap/paginate.jsx +153 -0
- package/src/bootstrap/radio-buttons.jsx +87 -0
- package/src/bootstrap/select.jsx +110 -0
- package/src/bootstrap/sort-link.jsx +102 -0
- package/src/collection-loader.jsx +7 -8
- package/src/inputs/auto-submit.mjs +37 -0
- package/src/inputs/checkbox.jsx +97 -0
- package/src/inputs/checkboxes.jsx +113 -0
- package/src/inputs/id-for-component.mjs +15 -0
- package/src/inputs/input-wrapper.jsx +170 -0
- package/src/inputs/input.jsx +235 -0
- package/src/inputs/money.jsx +177 -0
- package/src/inputs/name-for-component.mjs +15 -0
- package/src/inputs/select.jsx +87 -0
- package/src/model-class-require.mjs +1 -1
- package/src/model-name.mjs +6 -2
- package/src/params.mjs +7 -0
- package/src/super-admin/index-page/index.jsx +42 -0
- package/src/super-admin/index.jsx +46 -0
- package/src/super-admin/layout/header/index.jsx +60 -0
- package/src/super-admin/layout/header/style.scss +124 -0
- package/src/super-admin/layout/index.jsx +156 -0
- package/src/super-admin/layout/menu/index.jsx +116 -0
- package/src/super-admin/layout/menu/menu-content.jsx +55 -0
- package/src/super-admin/layout/menu/menu-item/index.jsx +27 -0
- package/src/super-admin/layout/menu/menu-item/style.scss +30 -0
- package/src/super-admin/layout/menu/style.scss +103 -0
- package/src/super-admin/layout/no-access.jsx +16 -0
- package/src/super-admin/layout/style.scss +25 -0
- package/src/super-admin/show-page.jsx +9 -0
- package/src/table/column-identifier.mjs +23 -0
- package/src/table/column-visible.mjs +7 -0
- package/src/table/model-row.jsx +182 -0
- package/src/table/select-calculator.mjs +48 -0
- package/src/table/style.scss +72 -0
- package/src/table/table-settings.js +175 -0
- package/src/table/table.jsx +498 -0
- package/src/table/variables.scss +11 -0
- package/src/table/with-breakpoint.jsx +48 -0
package/src/params.mjs
CHANGED
|
@@ -15,6 +15,13 @@ export default class Params {
|
|
|
15
15
|
return incorporator.merge()
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
static withParams (params, opts = {}) {
|
|
19
|
+
const newParams = qs.stringify(params)
|
|
20
|
+
const newPath = `${location.pathname}?${newParams}`
|
|
21
|
+
|
|
22
|
+
return newPath
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
static changeParams (given, opts = {}) {
|
|
19
26
|
const params = Params.change(given)
|
|
20
27
|
const newParams = qs.stringify(params)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {digg, digs} from "diggerize"
|
|
2
|
+
import Params from "../../params"
|
|
3
|
+
import Table from "../../table/table"
|
|
4
|
+
|
|
5
|
+
export default class ApiMakerSuperAdminIndexPage extends React.PureComponent {
|
|
6
|
+
static propTypes = {
|
|
7
|
+
currentUser: PropTypes.object,
|
|
8
|
+
modelClass: PropTypes.func.isRequired,
|
|
9
|
+
queryParams: PropTypes.object.isRequired
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render() {
|
|
13
|
+
const {currentUser, modelClass} = digs(this.props, "currentUser", "modelClass")
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<Table
|
|
18
|
+
columns={digg(this, "columns")}
|
|
19
|
+
currentUser={currentUser}
|
|
20
|
+
modelClass={modelClass}
|
|
21
|
+
viewModelPath={digg(this, "viewModelPath")}
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
columns = () => {
|
|
28
|
+
return [
|
|
29
|
+
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
viewModelPath = (args) => {
|
|
34
|
+
const argName = digg(this.props.modelClass.modelClassData(), "camelizedLower")
|
|
35
|
+
const model = digg(args, argName)
|
|
36
|
+
|
|
37
|
+
return Params.withParams({
|
|
38
|
+
model: this.props.modelClass.modelClassData().name,
|
|
39
|
+
model_id: model.primaryKey()
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {digs} from "diggerize"
|
|
2
|
+
import IndexPage from "./index-page"
|
|
3
|
+
import Layout from "./layout"
|
|
4
|
+
import * as modelsModule from "@kaspernj/api-maker/src/models.mjs.erb"
|
|
5
|
+
import ShowPage from "./show-page"
|
|
6
|
+
import withQueryParams from "on-location-changed/src/with-query-params"
|
|
7
|
+
|
|
8
|
+
class ApiMakerSuperAdmin extends React.PureComponent {
|
|
9
|
+
static propTypes = {
|
|
10
|
+
currentUser: PropTypes.object
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
const {currentUser} = this.props
|
|
15
|
+
const {queryParams} = digs(this.props, "queryParams")
|
|
16
|
+
const pageToShow = this.pageToShow()
|
|
17
|
+
let modelClass
|
|
18
|
+
|
|
19
|
+
if (queryParams.model) modelClass = modelsModule[queryParams.model]
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Layout>
|
|
23
|
+
{pageToShow == "index" &&
|
|
24
|
+
<IndexPage currentUser={currentUser} modelClass={modelClass} queryParams={queryParams} />
|
|
25
|
+
}
|
|
26
|
+
{pageToShow == "show" &&
|
|
27
|
+
<ShowPage modelClass={modelClass} modelId={queryParams.modelId} />
|
|
28
|
+
}
|
|
29
|
+
</Layout>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pageToShow() {
|
|
34
|
+
const {queryParams} = digs(this.props, "queryParams")
|
|
35
|
+
|
|
36
|
+
if (queryParams.model && queryParams.model_id) {
|
|
37
|
+
return "show"
|
|
38
|
+
} else if (queryParams.model) {
|
|
39
|
+
return "index"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return "welcome"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default withQueryParams(ApiMakerSuperAdmin)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import "./style"
|
|
2
|
+
|
|
3
|
+
export default class ApiMakerSuperAdminLayoutHeader extends BaseComponent {
|
|
4
|
+
static propTypes = PropTypesExact({
|
|
5
|
+
actions: PropTypes.node,
|
|
6
|
+
onTriggerMenu: PropTypes.func.isRequired,
|
|
7
|
+
title: PropTypes.string
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
headerActionsRef = React.createRef()
|
|
11
|
+
shape = new Shape(this, {headerActionsActive: false})
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
const {headerActionsRef} = digs(this, "headerActionsRef")
|
|
15
|
+
const {onGearsClicked} = digs(this, "onGearsClicked")
|
|
16
|
+
const {actions, onTriggerMenu, title} = this.props
|
|
17
|
+
const {headerActionsActive} = digs(this.shape, "headerActionsActive")
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="components--admin--layout--header">
|
|
21
|
+
<EventListener event="mouseup" onCalled={digg(this, "onWindowMouseUp")} target={window} />
|
|
22
|
+
<div className="header-title-container">
|
|
23
|
+
{title}
|
|
24
|
+
</div>
|
|
25
|
+
{actions &&
|
|
26
|
+
<div className="header-actions-container" data-active={headerActionsActive}>
|
|
27
|
+
<div className="header-actions" ref={headerActionsRef}>
|
|
28
|
+
{actions}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
}
|
|
32
|
+
<div className="burger-menu-container">
|
|
33
|
+
{actions &&
|
|
34
|
+
<a className="actions-link" href="#" onClick={onGearsClicked}>
|
|
35
|
+
<i className="fa fa-gear" />
|
|
36
|
+
</a>
|
|
37
|
+
}
|
|
38
|
+
<a className="burger-menu-link" href="#" onClick={onTriggerMenu}>
|
|
39
|
+
<i className="fa fa-bars" />
|
|
40
|
+
</a>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onGearsClicked = (e) => {
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
this.shape.set({
|
|
49
|
+
headerActionsActive: !this.shape.headerActionsActive
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
onWindowMouseUp = (e) => {
|
|
54
|
+
const {headerActionsRef} = digs(this, "headerActionsRef")
|
|
55
|
+
const {headerActionsActive} = digs(this.shape, "headerActionsActive")
|
|
56
|
+
|
|
57
|
+
// Close the header actions menu if clicked happened outside
|
|
58
|
+
if (headerActionsActive && headerActionsRef.current && !headerActionsRef.current.contains(e.target)) this.shape.set({headerActionsActive: false})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
@import "stylesheets/variables";
|
|
2
|
+
|
|
3
|
+
.components--admin--layout--header {
|
|
4
|
+
top: 0;
|
|
5
|
+
display: flex;
|
|
6
|
+
height: 100px;
|
|
7
|
+
align-items: center;
|
|
8
|
+
padding-right: 30px;
|
|
9
|
+
padding-left: 30px;
|
|
10
|
+
background: #fff;
|
|
11
|
+
color: #282a33;
|
|
12
|
+
|
|
13
|
+
@media (max-width: $sm-to) {
|
|
14
|
+
position: absolute;
|
|
15
|
+
width: 100%;
|
|
16
|
+
|
|
17
|
+
.header-actions-container {
|
|
18
|
+
position: fixed;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: 0;
|
|
21
|
+
|
|
22
|
+
display: flex;
|
|
23
|
+
width: 100vw;
|
|
24
|
+
height: 100vh;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
|
|
28
|
+
background: rgba(#000, .8);
|
|
29
|
+
|
|
30
|
+
&[data-active="false"] {
|
|
31
|
+
display: none;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.header-actions {
|
|
36
|
+
min-width: 80vw;
|
|
37
|
+
max-width: 100vw;
|
|
38
|
+
background: #fff;
|
|
39
|
+
|
|
40
|
+
.action-button {
|
|
41
|
+
display: block;
|
|
42
|
+
padding: 11px;
|
|
43
|
+
|
|
44
|
+
.fa {
|
|
45
|
+
margin-right: 5px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
+ .action-button {
|
|
49
|
+
border-top: 1px solid #c9c9c9;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:link,
|
|
53
|
+
&:visited {
|
|
54
|
+
color: #000;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&:active,
|
|
58
|
+
&:hover {
|
|
59
|
+
background: #dddcf0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (min-width: $md-from) {
|
|
66
|
+
position: fixed;
|
|
67
|
+
left: 250px;
|
|
68
|
+
width: calc(100% - 250px);
|
|
69
|
+
|
|
70
|
+
.header-actions-container {
|
|
71
|
+
margin-left: auto;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.header-actions {
|
|
75
|
+
.action-button {
|
|
76
|
+
display: inline-block;
|
|
77
|
+
padding: 7px 10px;
|
|
78
|
+
border: 1px solid #cbd5e1;
|
|
79
|
+
margin-right: 4px;
|
|
80
|
+
margin-bottom: 4px;
|
|
81
|
+
border-radius: 5px;
|
|
82
|
+
font-size: 13px;
|
|
83
|
+
|
|
84
|
+
&:link,
|
|
85
|
+
&:visited {
|
|
86
|
+
color: #000;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media (min-width: $lg-from) {
|
|
93
|
+
left: 290px;
|
|
94
|
+
width: calc(100% - 290px);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.burger-menu-container {
|
|
98
|
+
@media (max-width: $sm-to) {
|
|
99
|
+
margin-left: auto;
|
|
100
|
+
font-size: 28px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (min-width: $md-from) {
|
|
104
|
+
display: none;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.actions-link {
|
|
109
|
+
margin-right: 8px;
|
|
110
|
+
font-size: 22px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.actions-link,
|
|
114
|
+
.burger-menu-link {
|
|
115
|
+
&:link,
|
|
116
|
+
&:visited {
|
|
117
|
+
color: #1b1c1e;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.header-title-container {
|
|
122
|
+
font-size: 22px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import "./style"
|
|
2
|
+
import CommandsPool from "@kaspernj/api-maker/src/commands-pool"
|
|
3
|
+
import Header from "./header"
|
|
4
|
+
import Menu from "./menu"
|
|
5
|
+
|
|
6
|
+
const UsersSignIn = React.lazy(() => import("components/users/sign-in"))
|
|
7
|
+
const NoAccess = React.lazy(() => import("./no-access"))
|
|
8
|
+
|
|
9
|
+
class ApiMakerSuperAdminLayout extends React.PureComponent {
|
|
10
|
+
static defaultProps = {
|
|
11
|
+
requireAdmin: true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static propTypes = PropTypesExact({
|
|
15
|
+
actions: PropTypes.node,
|
|
16
|
+
active: PropTypes.string,
|
|
17
|
+
children: PropTypes.node,
|
|
18
|
+
className: PropTypes.string,
|
|
19
|
+
currentCustomer: PropTypes.instanceOf(User),
|
|
20
|
+
currentCustomerId: PropTypes.string,
|
|
21
|
+
currentUser: PropTypes.instanceOf(User),
|
|
22
|
+
headTitle: PropTypes.string,
|
|
23
|
+
headerTitle: PropTypes.string,
|
|
24
|
+
requireAdmin: PropTypes.bool.isRequired
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
componentDidMount() {
|
|
28
|
+
CommandsPool.current().globalRequestData.layout = "admin"
|
|
29
|
+
CommandsPool.current().globalRequestData.locale = I18n.locale
|
|
30
|
+
|
|
31
|
+
this.setDocumentTitle()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
componentDidUpdate() {
|
|
35
|
+
this.setDocumentTitle()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setDocumentTitle() {
|
|
39
|
+
const headTitle = this.props.headTitle || this.props.headerTitle
|
|
40
|
+
|
|
41
|
+
if (headTitle) {
|
|
42
|
+
document.title = headTitle
|
|
43
|
+
} else {
|
|
44
|
+
document.title = "Wooftech"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
shape = new Shape(this, {
|
|
49
|
+
menuTriggered: false
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
render() {
|
|
53
|
+
const {
|
|
54
|
+
actions,
|
|
55
|
+
active,
|
|
56
|
+
children,
|
|
57
|
+
className,
|
|
58
|
+
currentCustomer,
|
|
59
|
+
currentCustomerId,
|
|
60
|
+
currentUser,
|
|
61
|
+
headerTitle,
|
|
62
|
+
menu,
|
|
63
|
+
requireAdmin,
|
|
64
|
+
...restProps
|
|
65
|
+
} = this.props
|
|
66
|
+
const {menuTriggered} = digs(this.shape, "menuTriggered")
|
|
67
|
+
const noAccess = this.noAccess()
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className={classNames("components--admin--layout", className)} data-menu-triggered={menuTriggered} {...restProps}>
|
|
71
|
+
<Menu
|
|
72
|
+
active={active}
|
|
73
|
+
noAccess={noAccess}
|
|
74
|
+
onRequestMenuClose={digg(this, "onRequestMenuClose")}
|
|
75
|
+
triggered={menuTriggered}
|
|
76
|
+
/>
|
|
77
|
+
<Header actions={actions} onTriggerMenu={digg(this, "onTriggerMenu")} title={headerTitle} />
|
|
78
|
+
<div className="app-layout-content-container">
|
|
79
|
+
{noAccess &&
|
|
80
|
+
<>
|
|
81
|
+
<NoAccess />
|
|
82
|
+
{currentUser &&
|
|
83
|
+
<>
|
|
84
|
+
<div className="mb-4">
|
|
85
|
+
{I18n.t("js.components.app_layout.try_signing_out_and_in_with_a_different_user")}
|
|
86
|
+
</div>
|
|
87
|
+
{(isCurrentUserA("teacher") || isCurrentUserA("student")) &&
|
|
88
|
+
<div className="mb-4">
|
|
89
|
+
{this.clickHereToAccessTheUserUniverse()}
|
|
90
|
+
</div>
|
|
91
|
+
}
|
|
92
|
+
</>
|
|
93
|
+
}
|
|
94
|
+
{!currentUser &&
|
|
95
|
+
<>
|
|
96
|
+
<div className="mb-4">
|
|
97
|
+
{I18n.t("js.components.app_layout.try_signing_in")}
|
|
98
|
+
</div>
|
|
99
|
+
<UsersSignIn />
|
|
100
|
+
</>
|
|
101
|
+
}
|
|
102
|
+
</>
|
|
103
|
+
}
|
|
104
|
+
{!noAccess && children}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
clickHereToAccessTheUserUniverse() {
|
|
111
|
+
const replaces = [
|
|
112
|
+
{
|
|
113
|
+
component: (
|
|
114
|
+
<Link key="here-user-universe-link" to={Routes.userRootPath()}>
|
|
115
|
+
{I18n.t("js.components.app_layout.here")}
|
|
116
|
+
</Link>
|
|
117
|
+
),
|
|
118
|
+
text: "%{here}"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
component: (
|
|
122
|
+
<Link key="user-universe-link" to={Routes.userRootPath()}>
|
|
123
|
+
{I18n.t("js.components.app_layout.user_universe")}
|
|
124
|
+
</Link>
|
|
125
|
+
),
|
|
126
|
+
text: "%{user_universe}"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<TextComponentReplace
|
|
132
|
+
replaces={replaces}
|
|
133
|
+
text={I18n.t("js.components.app_layout.click_here_to_access_the_user_universe")}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onRequestMenuClose = () => this.shape.set({menuTriggered: false})
|
|
139
|
+
|
|
140
|
+
onTriggerMenu = (e) => {
|
|
141
|
+
e.preventDefault()
|
|
142
|
+
|
|
143
|
+
this.shape.set({menuTriggered: !this.shape.menuTriggered})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
noAccess() {
|
|
147
|
+
const {currentUser, requireAdmin} = digs(this.props, "currentUser", "requireAdmin")
|
|
148
|
+
|
|
149
|
+
if (requireAdmin && currentUser && !isCurrentUserA("admin") && !isCurrentUserA("hacker")) return true
|
|
150
|
+
if (requireAdmin && !currentUser) return true
|
|
151
|
+
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default withCurrentUser(ApiMakerSuperAdminLayout)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import "./style"
|
|
2
|
+
import {PopupMenu, PopupMenuItem} from "components/popup-menu"
|
|
3
|
+
import MenuContent from "./menu-content"
|
|
4
|
+
import MenuItem from "./menu-item"
|
|
5
|
+
|
|
6
|
+
class ComponentsAdminLayoutMenu extends BaseComponent {
|
|
7
|
+
static propTypes = PropTypesExact({
|
|
8
|
+
active: PropTypes.string,
|
|
9
|
+
currentUser: PropTypes.instanceOf(User),
|
|
10
|
+
noAccess: PropTypes.bool.isRequired,
|
|
11
|
+
onRequestMenuClose: PropTypes.func.isRequired,
|
|
12
|
+
triggered: PropTypes.bool.isRequired
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
menuUserItemsRef = React.createRef()
|
|
16
|
+
rootRef = React.createRef()
|
|
17
|
+
shape = new Shape(this, {userMenuItemOpen: false})
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
const {menuUserItemsRef, onUserItemsClicked, rootRef} = digs(this, "menuUserItemsRef", "onUserItemsClicked", "rootRef")
|
|
21
|
+
const {active} = this.props
|
|
22
|
+
const {
|
|
23
|
+
currentUser,
|
|
24
|
+
noAccess,
|
|
25
|
+
triggered
|
|
26
|
+
} = digs(
|
|
27
|
+
this.props,
|
|
28
|
+
"currentUser",
|
|
29
|
+
"noAccess",
|
|
30
|
+
"triggered"
|
|
31
|
+
)
|
|
32
|
+
const {userMenuItemOpen} = digs(this.shape, "userMenuItemOpen")
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="components--admin--layout--menu" data-triggered={triggered} ref={rootRef}>
|
|
36
|
+
<EventListener event="mouseup" onCalled={digg(this, "onWindowMouseUp")} target={window} />
|
|
37
|
+
<div className="menu-logo">
|
|
38
|
+
<Link className="menu-logo-link" to={Routes.adminRootPath()}>
|
|
39
|
+
Admin
|
|
40
|
+
</Link>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="menu-items-center">
|
|
43
|
+
{!noAccess &&
|
|
44
|
+
<MenuContent active={active} />
|
|
45
|
+
}
|
|
46
|
+
</div>
|
|
47
|
+
<div className="menu-items-bottom">
|
|
48
|
+
{currentUser &&
|
|
49
|
+
<div className="menu-user-section">
|
|
50
|
+
<div className="menu-user-icon">
|
|
51
|
+
<i className="fa fa-user" />
|
|
52
|
+
</div>
|
|
53
|
+
<div className="menu-user-name">
|
|
54
|
+
<div className="menu-user-name-container">
|
|
55
|
+
{currentUser.name()}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="menu-user-items" ref={menuUserItemsRef}>
|
|
59
|
+
{userMenuItemOpen &&
|
|
60
|
+
<PopupMenu>
|
|
61
|
+
<PopupMenuItem
|
|
62
|
+
children={I18n.t("js.components.app_layout.menu.notification_settings")}
|
|
63
|
+
className="notifications-settings-menu-item"
|
|
64
|
+
to="#"
|
|
65
|
+
/>
|
|
66
|
+
</PopupMenu>
|
|
67
|
+
}
|
|
68
|
+
<a className="menu-user-items-link" href="#" onClick={onUserItemsClicked}>
|
|
69
|
+
<i className="fa fa-ellipsis" />
|
|
70
|
+
</a>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
}
|
|
74
|
+
{currentUser &&
|
|
75
|
+
<MenuItem
|
|
76
|
+
active
|
|
77
|
+
className="sign-out-menu-item"
|
|
78
|
+
icon="sign-out-alt"
|
|
79
|
+
label={I18n.t("js.components.admin.layout.menu.sign_out")}
|
|
80
|
+
onClick={digg(this, "onSignOutClicked")}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onSignOutClicked = async (e) => {
|
|
89
|
+
e.preventDefault()
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await Devise.signOut()
|
|
93
|
+
FlashMessage.success(I18n.t("js.components.admin.layout.menu.you_have_been_signed_out"))
|
|
94
|
+
} catch (error) {
|
|
95
|
+
FlashMessage.errorResponse(error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onUserItemsClicked = (e) => {
|
|
100
|
+
e.preventDefault()
|
|
101
|
+
this.shape.set({userMenuItemOpen: !this.shape.userMenuItemOpen})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onWindowMouseUp = (e) => {
|
|
105
|
+
const {menuUserItemsRef, rootRef} = digs(this, "menuUserItemsRef", "rootRef")
|
|
106
|
+
const {triggered} = digs(this.props, "triggered")
|
|
107
|
+
|
|
108
|
+
// Close the menu if triggered (menu is open on mobile)
|
|
109
|
+
if (triggered && !rootRef.current.contains(e.target)) setTimeout(this.props.onRequestMenuClose)
|
|
110
|
+
|
|
111
|
+
// Close the user items menu if clicked happened outside of that
|
|
112
|
+
if (!menuUserItemsRef?.current?.contains(e.target)) this.shape.set({userMenuItemOpen: false})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default withCurrentUser(ComponentsAdminLayoutMenu)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import CanCanLoader from "@kaspernj/api-maker/src/can-can-loader"
|
|
2
|
+
import MenuItem from "components/admin/layout/menu/menu-item"
|
|
3
|
+
import Params from "../../../params"
|
|
4
|
+
import * as modelsModule from "@kaspernj/api-maker/src/models.mjs.erb"
|
|
5
|
+
|
|
6
|
+
const models = []
|
|
7
|
+
|
|
8
|
+
for (const modelKey of Object.keys(modelsModule)) {
|
|
9
|
+
const model = modelsModule[modelKey]
|
|
10
|
+
|
|
11
|
+
models.push(model)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const abilities = []
|
|
15
|
+
|
|
16
|
+
for (const model of models) {
|
|
17
|
+
abilities.push(
|
|
18
|
+
[model, ["index"]]
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default class ComponentsAdminLayoutMenuContent extends BaseComponent {
|
|
23
|
+
static propTypes = PropTypesExact({
|
|
24
|
+
active: PropTypes.string
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
shape = new Shape(this, {
|
|
28
|
+
canCan: undefined
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
render() {
|
|
32
|
+
const {active} = digs(this.props, "active")
|
|
33
|
+
const {canCan} = digs(this.shape, "canCan")
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<CanCanLoader abilities={abilities} component={this} />
|
|
38
|
+
{this.sortedModels().map((model) => canCan?.can("index", model) &&
|
|
39
|
+
<MenuItem
|
|
40
|
+
active={active}
|
|
41
|
+
icon="sitemap"
|
|
42
|
+
identifier="check-ins"
|
|
43
|
+
label={model.modelName().human({count: 2})}
|
|
44
|
+
key={model.modelClassData().name}
|
|
45
|
+
to={Params.withParams({model: model.modelClassData().name})}
|
|
46
|
+
/>
|
|
47
|
+
)}
|
|
48
|
+
</>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sortedModels() {
|
|
53
|
+
return models.sort((a, b) => a.modelName().human({count: 2}).toLowerCase().localeCompare(b.modelName().human({count: 2}).toLowerCase()))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "./style"
|
|
2
|
+
|
|
3
|
+
export default class ComponentsAdminLayoutMenuMenuItem extends BaseComponent {
|
|
4
|
+
static propTypes = {
|
|
5
|
+
active: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
|
6
|
+
className: PropTypes.string,
|
|
7
|
+
icon: PropTypes.string.isRequired,
|
|
8
|
+
label: PropTypes.node
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
const {active, children, className, icon, identifier, label, to, ...restProps} = this.props
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Link
|
|
16
|
+
className={classNames("components--admin--layout--menu--menu-item", className)}
|
|
17
|
+
data-active={active === true || active == identifier}
|
|
18
|
+
data-identifier={identifier}
|
|
19
|
+
to={to || "#"}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
<i className={`fa fa-fw fa-${icon} menu-item-icon`} />
|
|
23
|
+
{children || label}
|
|
24
|
+
</Link>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.components--admin--layout--menu--menu-item {
|
|
2
|
+
display: flex;
|
|
3
|
+
width: 80%;
|
|
4
|
+
align-items: center;
|
|
5
|
+
padding: 10px 14px;
|
|
6
|
+
margin-right: auto;
|
|
7
|
+
margin-left: auto;
|
|
8
|
+
text-decoration: none;
|
|
9
|
+
|
|
10
|
+
&:link,
|
|
11
|
+
&:visited {
|
|
12
|
+
color: #6f6f71;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&[data-active="true"],
|
|
16
|
+
&:hover {
|
|
17
|
+
background: #323435;
|
|
18
|
+
border-radius: 7px;
|
|
19
|
+
color: #b9b9bb;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.menu-item-icon {
|
|
23
|
+
margin-right: 4px;
|
|
24
|
+
font-size: 12px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
+ .components--admin--layout--menu--menu-item {
|
|
28
|
+
margin-top: 6px;
|
|
29
|
+
}
|
|
30
|
+
}
|