@@ -9,8 +9,14 @@ import {
99 ToastContainer ,
1010 Toast ,
1111 Alert ,
12+ Tabs ,
13+ Tab ,
1214} from 'react-bootstrap' ;
1315import { useStore } from '../stores/storeConfig' ;
16+ import UserGrantsTab from '../components/admin/UserGrantsTab' ;
17+ import './AdminPage.css' ;
18+
19+ type AdminTab = 'grant-users' | 'grants' ;
1420
1521const AdminPage : React . FC = observer ( ( ) => {
1622 const { grantStore } = useStore ( ) ;
@@ -21,18 +27,12 @@ const AdminPage: React.FC = observer(() => {
2127 errorMessage,
2228 fetchGrants,
2329 createGrant,
24- deleteGrant,
25- assignGrantToUser,
26- removeGrantFromUser,
2730 clearMessages,
2831 } = grantStore ;
2932
33+ const [ activeTab , setActiveTab ] = React . useState < AdminTab > ( 'grant-users' ) ;
3034 const [ createName , setCreateName ] = React . useState ( '' ) ;
3135 const [ createDescription , setCreateDescription ] = React . useState ( '' ) ;
32- const [ assignEmail , setAssignEmail ] = React . useState ( '' ) ;
33- const [ assignGrant , setAssignGrant ] = React . useState ( '' ) ;
34- const [ removeEmail , setRemoveEmail ] = React . useState ( '' ) ;
35- const [ removeGrant , setRemoveGrant ] = React . useState ( '' ) ;
3636
3737 useEffect ( ( ) => {
3838 fetchGrants ( ) ;
@@ -45,155 +45,79 @@ const AdminPage: React.FC = observer(() => {
4545 }
4646 } , [ successMessage , errorMessage , clearMessages ] ) ;
4747
48- useEffect ( ( ) => {
49- if ( grants . length > 0 && ! assignGrant ) {
50- setAssignGrant ( grants [ 0 ] . name ) ;
51- }
52- if ( grants . length > 0 && ! removeGrant ) {
53- setRemoveGrant ( grants [ 0 ] . name ) ;
54- }
55- } , [ grants , assignGrant , removeGrant ] ) ;
56-
5748 const handleCreateGrant = async ( e : React . FormEvent ) => {
5849 e . preventDefault ( ) ;
5950 await createGrant ( { name : createName . trim ( ) , description : createDescription . trim ( ) } ) ;
6051 setCreateName ( '' ) ;
6152 setCreateDescription ( '' ) ;
6253 } ;
6354
64- const handleAssignGrant = async ( e : React . FormEvent ) => {
65- e . preventDefault ( ) ;
66- await assignGrantToUser ( { email : assignEmail . trim ( ) , grant : assignGrant } ) ;
67- setAssignEmail ( '' ) ;
68- } ;
69-
70- const handleRemoveGrant = async ( e : React . FormEvent ) => {
71- e . preventDefault ( ) ;
72- await removeGrantFromUser ( { email : removeEmail . trim ( ) , grant : removeGrant } ) ;
73- setRemoveEmail ( '' ) ;
74- } ;
75-
76- const handleDeleteGrant = async ( grantName : string ) => {
77- if ( window . confirm ( `Delete grant "${ grantName } "? This will remove all user associations.` ) ) {
78- await deleteGrant ( grantName ) ;
79- }
80- } ;
81-
8255 return (
83- < Container className = 'py-4' >
56+ < Container className = 'py-4 admin-page' dir = 'ltr '>
8457 < h2 className = 'mb-4' > Grant Management</ h2 >
8558
86- < h4 className = 'mb-3' > Grants</ h4 >
87- { loading ? (
88- < Spinner animation = 'border' />
89- ) : grants . length === 0 ? (
90- < Alert variant = 'info' > No grants found.</ Alert >
91- ) : (
92- < Table striped bordered hover responsive className = 'mb-4' >
93- < thead >
94- < tr >
95- < th > Name</ th >
96- < th > Description</ th >
97- < th > Actions</ th >
98- </ tr >
99- </ thead >
100- < tbody >
101- { grants . map ( ( grant ) => (
102- < tr key = { grant . id } >
103- < td > { grant . name } </ td >
104- < td > { grant . description } </ td >
105- < td >
106- < Button
107- variant = 'danger'
108- size = 'sm'
109- onClick = { ( ) => handleDeleteGrant ( grant . name ) }
110- >
111- Delete
112- </ Button >
113- </ td >
114- </ tr >
115- ) ) }
116- </ tbody >
117- </ Table >
118- ) }
119-
120- < h4 className = 'mb-3' > Create Grant</ h4 >
121- < Form onSubmit = { handleCreateGrant } className = 'mb-4' >
122- < Form . Group className = 'mb-3' >
123- < Form . Label > Name</ Form . Label >
124- < Form . Control
125- type = 'text'
126- value = { createName }
127- onChange = { ( e ) => setCreateName ( e . target . value ) }
128- required
129- />
130- </ Form . Group >
131- < Form . Group className = 'mb-3' >
132- < Form . Label > Description</ Form . Label >
133- < Form . Control
134- as = 'textarea'
135- rows = { 2 }
136- value = { createDescription }
137- onChange = { ( e ) => setCreateDescription ( e . target . value ) }
138- required
139- />
140- </ Form . Group >
141- < Button type = 'submit' variant = 'primary' >
142- Create Grant
143- </ Button >
144- </ Form >
145-
146- < h4 className = 'mb-3' > Assign Grant to User</ h4 >
147- < Form onSubmit = { handleAssignGrant } className = 'mb-4' >
148- < Form . Group className = 'mb-3' >
149- < Form . Label > User Email</ Form . Label >
150- < Form . Control
151- type = 'email'
152- value = { assignEmail }
153- onChange = { ( e ) => setAssignEmail ( e . target . value ) }
154- required
155- />
156- </ Form . Group >
157- < Form . Group className = 'mb-3' >
158- < Form . Label > Grant</ Form . Label >
159- < Form . Select value = { assignGrant } onChange = { ( e ) => setAssignGrant ( e . target . value ) } required >
160- { grants . map ( ( grant ) => (
161- < option key = { grant . id } value = { grant . name } >
162- { grant . name }
163- </ option >
164- ) ) }
165- </ Form . Select >
166- </ Form . Group >
167- < Button type = 'submit' variant = 'primary' disabled = { grants . length === 0 } >
168- Assign Grant
169- </ Button >
170- </ Form >
171-
172- < h4 className = 'mb-3' > Remove Grant from User</ h4 >
173- < Form onSubmit = { handleRemoveGrant } className = 'mb-4' >
174- < Form . Group className = 'mb-3' >
175- < Form . Label > User Email</ Form . Label >
176- < Form . Control
177- type = 'email'
178- value = { removeEmail }
179- onChange = { ( e ) => setRemoveEmail ( e . target . value ) }
180- required
181- />
182- </ Form . Group >
183- < Form . Group className = 'mb-3' >
184- < Form . Label > Grant</ Form . Label >
185- < Form . Select value = { removeGrant } onChange = { ( e ) => setRemoveGrant ( e . target . value ) } required >
186- { grants . map ( ( grant ) => (
187- < option key = { grant . id } value = { grant . name } >
188- { grant . name }
189- </ option >
190- ) ) }
191- </ Form . Select >
192- </ Form . Group >
193- < Button type = 'submit' variant = 'warning' disabled = { grants . length === 0 } >
194- Remove Grant
195- </ Button >
196- </ Form >
59+ < Tabs
60+ activeKey = { activeTab }
61+ onSelect = { ( key ) => setActiveTab ( key as AdminTab ) }
62+ id = 'admin-grant-tabs'
63+ className = 'mb-4'
64+ >
65+ < Tab eventKey = 'grant-users' title = 'User Grants' >
66+ < UserGrantsTab />
67+ </ Tab >
68+
69+ < Tab eventKey = 'grants' title = 'Grants' >
70+ < h4 className = 'mb-3 mt-3' > Grants</ h4 >
71+ { loading ? (
72+ < Spinner animation = 'border' />
73+ ) : grants . length === 0 ? (
74+ < Alert variant = 'info' > No grants found.</ Alert >
75+ ) : (
76+ < Table striped bordered hover responsive className = 'mb-4' >
77+ < thead >
78+ < tr >
79+ < th > Name</ th >
80+ < th > Description</ th >
81+ </ tr >
82+ </ thead >
83+ < tbody >
84+ { grants . map ( ( grant ) => (
85+ < tr key = { grant . id } >
86+ < td > { grant . name } </ td >
87+ < td > { grant . description } </ td >
88+ </ tr >
89+ ) ) }
90+ </ tbody >
91+ </ Table >
92+ ) }
93+
94+ < h4 className = 'mb-3' > Create Grant</ h4 >
95+ < Form onSubmit = { handleCreateGrant } >
96+ < Form . Group className = 'mb-3' >
97+ < Form . Label > Name</ Form . Label >
98+ < Form . Control
99+ type = 'text'
100+ value = { createName }
101+ onChange = { ( e ) => setCreateName ( e . target . value ) }
102+ required
103+ />
104+ </ Form . Group >
105+ < Form . Group className = 'mb-3' >
106+ < Form . Label > Description</ Form . Label >
107+ < Form . Control
108+ as = 'textarea'
109+ rows = { 2 }
110+ value = { createDescription }
111+ onChange = { ( e ) => setCreateDescription ( e . target . value ) }
112+ required
113+ />
114+ </ Form . Group >
115+ < Button type = 'submit' variant = 'primary' >
116+ Create Grant
117+ </ Button >
118+ </ Form >
119+ </ Tab >
120+ </ Tabs >
197121
198122 < ToastContainer position = 'top-center' className = 'p-3 custom-toast-container' style = { { zIndex : 1100 } } >
199123 { successMessage && (
0 commit comments