@kaspernj/api-maker 1.0.216 → 1.0.217

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.
Files changed (48) hide show
  1. package/package.json +2 -1
  2. package/src/base-model.mjs +1 -1
  3. package/src/bootstrap/attribute-row/basic-style.scss +9 -0
  4. package/src/bootstrap/attribute-row/index.jsx +84 -0
  5. package/src/bootstrap/attribute-rows.jsx +27 -0
  6. package/src/bootstrap/card.jsx +135 -0
  7. package/src/bootstrap/checkbox.jsx +79 -0
  8. package/src/bootstrap/checkboxes.jsx +122 -0
  9. package/src/bootstrap/index.js +0 -0
  10. package/src/bootstrap/input.jsx +160 -0
  11. package/src/bootstrap/invalid-feedback.jsx +31 -0
  12. package/src/bootstrap/live-table/model-row.jsx +150 -0
  13. package/src/bootstrap/live-table.jsx +399 -0
  14. package/src/bootstrap/paginate.jsx +153 -0
  15. package/src/bootstrap/radio-buttons.jsx +87 -0
  16. package/src/bootstrap/select.jsx +110 -0
  17. package/src/bootstrap/sort-link.jsx +102 -0
  18. package/src/collection-loader.jsx +7 -8
  19. package/src/inputs/auto-submit.mjs +37 -0
  20. package/src/inputs/checkbox.jsx +97 -0
  21. package/src/inputs/checkboxes.jsx +113 -0
  22. package/src/inputs/id-for-component.mjs +15 -0
  23. package/src/inputs/input-wrapper.jsx +170 -0
  24. package/src/inputs/input.jsx +235 -0
  25. package/src/inputs/money.jsx +177 -0
  26. package/src/inputs/name-for-component.mjs +15 -0
  27. package/src/inputs/select.jsx +87 -0
  28. package/src/model-class-require.mjs +1 -1
  29. package/src/model-name.mjs +1 -1
  30. package/src/super-admin/index.jsx +11 -0
  31. package/src/super-admin/layout/header/index.jsx +60 -0
  32. package/src/super-admin/layout/header/style.scss +124 -0
  33. package/src/super-admin/layout/index.jsx +156 -0
  34. package/src/super-admin/layout/menu/index.jsx +116 -0
  35. package/src/super-admin/layout/menu/menu-content.jsx +106 -0
  36. package/src/super-admin/layout/menu/menu-item/index.jsx +27 -0
  37. package/src/super-admin/layout/menu/menu-item/style.scss +30 -0
  38. package/src/super-admin/layout/menu/style.scss +103 -0
  39. package/src/super-admin/layout/style.scss +25 -0
  40. package/src/table/column-identifier.mjs +23 -0
  41. package/src/table/column-visible.mjs +7 -0
  42. package/src/table/model-row.jsx +182 -0
  43. package/src/table/select-calculator.mjs +48 -0
  44. package/src/table/style.scss +72 -0
  45. package/src/table/table-settings.js +175 -0
  46. package/src/table/table.jsx +498 -0
  47. package/src/table/variables.scss +11 -0
  48. package/src/table/with-breakpoint.jsx +48 -0
@@ -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,106 @@
1
+ import CanCanLoader from "@kaspernj/api-maker/src/can-can-loader"
2
+ import MenuItem from "components/admin/layout/menu/menu-item"
3
+
4
+ const abilities = [
5
+ [CheckIn, ["index"]],
6
+ [ClassStep, ["index"]],
7
+ [ScoreFactor, ["index"]],
8
+ [ScoreFactorGroup, ["index"]],
9
+ [School, ["index"]],
10
+ [SchoolClass, ["index"]],
11
+ [Survey, ["index"]],
12
+ [User, ["index"]]
13
+ ]
14
+
15
+ export default class ComponentsAdminLayoutMenuContent extends BaseComponent {
16
+ static propTypes = PropTypesExact({
17
+ active: PropTypes.string
18
+ })
19
+
20
+ shape = new Shape(this, {
21
+ canCan: undefined
22
+ })
23
+
24
+ render() {
25
+ const {active} = digs(this.props, "active")
26
+ const {canCan} = digs(this.shape, "canCan")
27
+
28
+ return (
29
+ <>
30
+ <CanCanLoader abilities={abilities} component={this} />
31
+ {canCan?.can("index", CheckIn) &&
32
+ <MenuItem
33
+ active={active}
34
+ icon="sitemap"
35
+ identifier="check-ins"
36
+ label={CheckIn.modelName().human({count: 2})}
37
+ to={Routes.adminCheckInsPath()}
38
+ />
39
+ }
40
+ {canCan?.can("index", ClassStep) &&
41
+ <MenuItem
42
+ active={active}
43
+ icon="sitemap"
44
+ identifier="class-steps"
45
+ label={ClassStep.modelName().human({count: 2})}
46
+ to={Routes.adminClassStepsPath()}
47
+ />
48
+ }
49
+ {canCan?.can("index", School) &&
50
+ <MenuItem
51
+ active={active}
52
+ icon="sitemap"
53
+ identifier="schools"
54
+ label={School.modelName().human({count: 2})}
55
+ to={Routes.adminSchoolsPath()}
56
+ />
57
+ }
58
+ {canCan?.can("index", SchoolClass) &&
59
+ <MenuItem
60
+ active={active}
61
+ icon="sitemap"
62
+ identifier="school-classes"
63
+ label={SchoolClass.modelName().human({count: 2})}
64
+ to={Routes.adminSchoolClassesPath()}
65
+ />
66
+ }
67
+ {canCan?.can("index", ScoreFactor) &&
68
+ <MenuItem
69
+ active={active}
70
+ icon="list-ol"
71
+ identifier="score-factors"
72
+ label={ScoreFactor.modelName().human({count: 2})}
73
+ to={Routes.adminScoreFactorsPath()}
74
+ />
75
+ }
76
+ {canCan?.can("index", ScoreFactorGroup) &&
77
+ <MenuItem
78
+ active={active}
79
+ icon="list-ol"
80
+ identifier="score-factor-groups"
81
+ label={ScoreFactorGroup.modelName().human({count: 2})}
82
+ to={Routes.adminScoreFactorGroupsPath()}
83
+ />
84
+ }
85
+ {canCan?.can("index", Survey) &&
86
+ <MenuItem
87
+ active={active}
88
+ icon="list-ol"
89
+ identifier="surveys"
90
+ label={Survey.modelName().human({count: 2})}
91
+ to={Routes.adminSurveysPath()}
92
+ />
93
+ }
94
+ {canCan?.can("index", User) &&
95
+ <MenuItem
96
+ active={active}
97
+ icon="users"
98
+ identifier="users"
99
+ label={User.modelName().human({count: 2})}
100
+ to={Routes.adminUsersPath()}
101
+ />
102
+ }
103
+ </>
104
+ )
105
+ }
106
+ }
@@ -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
+ }