Skip to content

Commit 4cb4f57

Browse files
Merge pull request #129 from niteeshkanna-sh/claude/stoic-rubin-fnglq7
Permissions, not just a role, when adding somebody
2 parents c2697c2 + 64b28d3 commit 4cb4f57

9 files changed

Lines changed: 897 additions & 50 deletions

File tree

‎admin/admin.css‎

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,9 +972,86 @@ label.finance-toggle {
972972
border: 1px solid var(--border); border-radius: 9px;
973973
}
974974

975-
.u-new { max-width: 640px; }
975+
.u-new { max-width: 920px; }
976+
.u-new .modal-row, .u-new > .field-group:not(.u-perm-block) { max-width: 640px; }
976977
.u-new .field-group { margin-bottom: 12px; }
977978

979+
/* ---------- Who may do what ---------- */
980+
981+
/* One person per row, shut unless they are an exception to their role. An
982+
account that is exactly its role has nothing to read, and twenty-seven
983+
ticks saying "yes, the standard" is twenty-seven things to scan past to
984+
reach the one person somebody opened this page for. */
985+
.u-perm-person {
986+
border: 1px solid var(--border); border-radius: 12px;
987+
padding: 0 14px; margin-bottom: 10px; background: var(--surface);
988+
}
989+
.u-perm-person > summary {
990+
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
991+
padding: 13px 0; cursor: pointer; list-style: none;
992+
}
993+
.u-perm-person > summary::-webkit-details-marker { display: none; }
994+
.u-perm-person > summary::after {
995+
content: '+'; margin-left: auto; color: var(--ink-faint);
996+
font-weight: 700; font-size: 1.05rem; line-height: 1;
997+
}
998+
.u-perm-person[open] > summary::after { content: '\2212'; }
999+
.u-perm-person[open] > summary { border-bottom: 1px solid var(--border); }
1000+
.u-perm-person > :not(summary) { margin: 14px 0; }
1001+
.u-perm-name { font-weight: 700; }
1002+
.u-perm-person > summary .field-hint { margin: 0; }
1003+
.u-perm-role { font-size: 0.82rem; color: var(--ink-dim); }
1004+
1005+
/* The tally under a role in the table above: "2 added, 1 removed". */
1006+
.u-perm-tally { display: block; margin-top: 3px; }
1007+
1008+
/* Columns that fit what is in them rather than a fixed count: eight groups of
1009+
between one and five permissions, which at one column each on a desk leaves
1010+
Reports as a column of one, and at four fixed columns on a phone is
1011+
unreadable. */
1012+
.u-perm-grid {
1013+
display: grid; gap: 12px;
1014+
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
1015+
}
1016+
.u-perm-group {
1017+
border: 1px solid var(--border); border-radius: 11px;
1018+
padding: 10px 13px 12px; margin: 0; min-width: 0;
1019+
}
1020+
.u-perm-group legend {
1021+
font-size: 0.71rem; font-weight: 700; text-transform: uppercase;
1022+
letter-spacing: 0.04em; color: var(--ink-faint); padding: 0 5px;
1023+
}
1024+
/* Written as .u-perm-grid .u-perm rather than .u-perm so it outranks
1025+
".field-group label { display: block }", which the create form's copy of
1026+
this grid sits inside. Without it the tick box stops being the start of a
1027+
row and the words wrap underneath it. */
1028+
.u-perm-grid .u-perm {
1029+
display: flex; align-items: flex-start; gap: 9px; margin: 0;
1030+
padding: 5px 0; font-size: 0.85rem; font-weight: 500; color: var(--ink);
1031+
cursor: pointer; line-height: 1.35;
1032+
}
1033+
.u-perm-grid .u-perm input[type="checkbox"] {
1034+
margin: 1px 0 0; flex-shrink: 0; width: 16px; height: 16px;
1035+
accent-color: var(--primary);
1036+
}
1037+
.u-perm-grid .u-perm input:disabled { cursor: default; }
1038+
.u-perm-grid .u-perm input:disabled + span { color: var(--ink-faint); }
1039+
1040+
/* The ones that remove something, or change a figure already counted. Marked
1041+
rather than separated out: they belong beside the thing they undo, and a
1042+
"dangerous" group of its own invites being ticked as a group. */
1043+
.u-perm-grid .u-perm.is-weighty > span::after {
1044+
content: ''; display: inline-block; vertical-align: middle;
1045+
width: 5px; height: 5px; border-radius: 50%;
1046+
background: var(--danger); opacity: 0.55; margin-left: 6px;
1047+
}
1048+
1049+
.u-perm-save { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
1050+
1051+
@media (max-width: 560px) {
1052+
.u-perm-grid { grid-template-columns: 1fr; }
1053+
}
1054+
9781055
/* ---------- Bookings ---------- */
9791056

9801057
.booking-card {

‎admin/sql/017_user_abilities.sql‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- What one person may do that their role does not say.
2+
--
3+
-- Five roles covered the business while everybody fitted one of them. They
4+
-- stop covering it the moment somebody nearly fits: the person who runs the
5+
-- counter and is also trusted to take the cash, the relative who should see
6+
-- the expenses and touch nothing. Both of those were a choice between a role
7+
-- that does too little and one that does far too much, and the panel had no
8+
-- third answer.
9+
--
10+
-- One row per difference, not per permission. An account with no rows is
11+
-- exactly its role, which is almost all of them; an account with two rows is
12+
-- "Accounts, and may also cancel a booking". Storing the differences rather
13+
-- than a snapshot of the whole set is what keeps that sentence true after the
14+
-- meaning of a role changes -- a snapshot would silently freeze whatever the
15+
-- role happened to grant on the day the account was made.
16+
--
17+
-- granted is 1 for something the role does not give and this person has, 0
18+
-- for something the role gives and this person does not. There is no third
19+
-- value: "as the role says" is the absence of a row.
20+
CREATE TABLE IF NOT EXISTS user_abilities (
21+
user_id INT UNSIGNED NOT NULL,
22+
23+
-- The permission as the server asks for it: 'payment.create'. Deliberately
24+
-- not a foreign key to a table of permissions -- the catalogue lives in
25+
-- src/abilities.php next to the code that enforces it, and a row naming a
26+
-- permission that no longer exists is ignored rather than fatal.
27+
ability VARCHAR(40) NOT NULL,
28+
29+
granted TINYINT(1) NOT NULL,
30+
31+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
32+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
33+
34+
-- One answer per person per permission, enforced here rather than trusted
35+
-- to the code: two rows disagreeing about whether somebody may void a
36+
-- payment is not a question the server should ever have to resolve.
37+
PRIMARY KEY (user_id, ability),
38+
39+
CONSTRAINT fk_user_abilities_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
40+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

0 commit comments

Comments
 (0)