66 getBatches , createBatch , updateBatch , deleteBatch ,
77 getSemesters , createSemester , updateSemester , deleteSemester ,
88 getSubjects , createSubject , updateSubject , deleteSubject ,
9+ getFolders , createFolder , updateFolder , deleteFolder ,
910 getNotes , deleteNote
1011} from "@/lib/firebase/firestore" ;
1112import { useAuth } from "@/lib/firebase/auth" ;
@@ -22,18 +23,21 @@ export default function ManageStructurePage() {
2223 const [ batches , setBatches ] = useState < any [ ] > ( [ ] ) ;
2324 const [ semesters , setSemesters ] = useState < any [ ] > ( [ ] ) ;
2425 const [ subjects , setSubjects ] = useState < any [ ] > ( [ ] ) ;
26+ const [ folders , setFolders ] = useState < any [ ] > ( [ ] ) ;
2527 const [ notes , setNotes ] = useState < any [ ] > ( [ ] ) ;
2628
2729 const [ selectedDept , setSelectedDept ] = useState < any > ( null ) ;
2830 const [ selectedBatch , setSelectedBatch ] = useState < any > ( null ) ;
2931 const [ selectedSem , setSelectedSem ] = useState < any > ( null ) ;
3032 const [ selectedSub , setSelectedSub ] = useState < any > ( null ) ;
33+ const [ selectedFolder , setSelectedFolder ] = useState < any > ( null ) ;
3134
3235 // Separate creation states
3336 const [ newDeptName , setNewDeptName ] = useState ( "" ) ;
3437 const [ newBatchName , setNewBatchName ] = useState ( "" ) ;
3538 const [ newSemName , setNewSemName ] = useState ( "" ) ;
3639 const [ newSubName , setNewSubName ] = useState ( "" ) ;
40+ const [ newFolderName , setNewFolderName ] = useState ( "" ) ;
3741
3842 const [ isCreating , setIsCreating ] = useState ( false ) ;
3943
@@ -49,23 +53,45 @@ export default function ManageStructurePage() {
4953 useEffect ( ( ) => { if ( selectedDept ) loadBatches ( selectedDept . id ) ; } , [ selectedDept ] ) ;
5054 useEffect ( ( ) => { if ( selectedBatch ) loadSemesters ( selectedBatch . id ) ; } , [ selectedBatch ] ) ;
5155 useEffect ( ( ) => { if ( selectedSem ) loadSubjects ( selectedSem . id ) ; } , [ selectedSem ] ) ;
52- useEffect ( ( ) => { if ( selectedSub ) loadNotes ( selectedSub . id ) ; } , [ selectedSub ] ) ;
56+ useEffect ( ( ) => { if ( selectedSub ) loadFolders ( selectedSub . id ) ; } , [ selectedSub ] ) ;
57+ useEffect ( ( ) => { if ( selectedSub ) loadNotes ( selectedSub . id ) ; } , [ selectedSub , selectedFolder ] ) ; // Reload notes when folder changes
5358
5459 async function loadDepartments ( ) {
5560 const depts = await getDepartments ( ) ;
5661 setDepartments ( depts ) ;
57- if ( depts . length > 0 && ! selectedDept ) {
58- // Optional: Auto-select first dept? Maybe NO, to avoid confusion?
59- // Let's keep auto-select off or only if user hasn't selected anything?
60- // Actually, auto-selecting can be annoying if I'm trying to create a new one.
61- // Let's NOT auto-select for now to simplify.
62- // if (!selectedDept) setSelectedDept(depts[0]);
63- }
6462 }
6563 async function loadBatches ( deptId : string ) { setBatches ( await getBatches ( deptId ) ) ; }
6664 async function loadSemesters ( batchId : string ) { setSemesters ( await getSemesters ( batchId ) ) ; }
6765 async function loadSubjects ( semId : string ) { setSubjects ( await getSubjects ( semId ) ) ; }
68- async function loadNotes ( subId : string ) { setNotes ( await getNotes ( subId ) ) ; }
66+ async function loadFolders ( subId : string ) { setFolders ( await getFolders ( subId ) ) ; }
67+ // async function loadNotes(subId: string) { setNotes(await getNotes(subId)); } // Replaced by filtering logic below or refined fetch
68+ // Actually, getNotes gets ALL notes for subject. We can filter on client side or fetch by folder.
69+ // Let's stick to getNotes(subId) for simplicity and filter in UI for now, OR better:
70+ // If we want to see "General Notes" vs "Folder Notes", we might want to fetch all and filter.
71+ // BUT the notes list in this Manage view is for "Managing content".
72+ // Let's show ALL notes for the subject, but indicate which folder they are in, OR follow the drill down.
73+ // Let's follow drill down: Select Folder -> Show Notes in Folder. Select NULL Folder -> Show General Notes?
74+ // User requested "Folders" column. So:
75+ // Dept -> Batch -> Sem -> Sub -> [Folders List] -> [Notes List (filtered by selected folder)]
76+ async function loadNotes ( subId : string ) {
77+ // Fetch all notes for the subject to be safe, then we filter in UI or here?
78+ // Let's just use getNotes(subId) and filter by selectedFolder in render/state.
79+ // Wait, if I delete a folder, what happens to notes?
80+ // For now, simple fetch.
81+ const allNotes : any [ ] = await getNotes ( subId ) ;
82+ if ( selectedFolder ) {
83+ setNotes ( allNotes . filter ( n => n . folderId === selectedFolder . id ) ) ;
84+ } else {
85+ // Show ONLY general notes (no folder) when no folder selected?
86+ // OR show ALL notes if no folder selected?
87+ // "Manage Structure" usually implies drill down.
88+ // Let's show General Notes (folderId is null) when 'General' is implicitly selected?
89+ // Actually, let's treat "No Folder Selected" as "Viewing General Notes" implies strict hierarchy.
90+ // Let's try: When Subject selected -> Show Folders AND "General Notes" pseudo-folder?
91+ // Or just: If File has no folder, it shows up when NO folder is selected.
92+ setNotes ( allNotes . filter ( n => ! n . folderId ) ) ;
93+ }
94+ }
6995
7096 async function handleCreateDept ( ) {
7197 if ( ! newDeptName . trim ( ) ) return ;
@@ -111,6 +137,23 @@ export default function ManageStructurePage() {
111137 finally { setIsCreating ( false ) ; }
112138 }
113139
140+ async function handleCreateFolder ( ) {
141+ if ( ! newFolderName . trim ( ) || ! selectedSub ) return ;
142+ setIsCreating ( true ) ;
143+ try {
144+ await createFolder ( {
145+ departmentId : selectedDept . id ,
146+ batchId : selectedBatch . id ,
147+ semesterId : selectedSem . id ,
148+ subjectId : selectedSub . id ,
149+ name : newFolderName
150+ } ) ;
151+ await loadFolders ( selectedSub . id ) ;
152+ setNewFolderName ( "" ) ;
153+ } catch ( e ) { console . error ( e ) ; alert ( "Failed to create." ) ; }
154+ finally { setIsCreating ( false ) ; }
155+ }
156+
114157 async function handleUpdate ( ) {
115158 if ( ! editName . trim ( ) || ! editingItem ) return ;
116159 try {
@@ -126,13 +169,16 @@ export default function ManageStructurePage() {
126169 } else if ( editingItem . type === 'sub' ) {
127170 await updateSubject ( editingItem . id , editName ) ;
128171 await loadSubjects ( selectedSem . id ) ;
172+ } else if ( editingItem . type === 'folder' ) {
173+ await updateFolder ( editingItem . id , editName ) ;
174+ await loadFolders ( selectedSub . id ) ;
129175 }
130176 setEditingItem ( null ) ;
131177 setEditName ( "" ) ;
132178 } catch ( e ) { console . error ( e ) ; alert ( "Failed to update." ) ; }
133179 }
134180
135- async function handleDelete ( item : any , type : 'dept' | 'batch' | 'sem' | 'sub' | 'note' ) {
181+ async function handleDelete ( item : any , type : 'dept' | 'batch' | 'sem' | 'sub' | 'folder' | ' note') {
136182 // Optimistic UI Update & Undo Callback setup
137183 let undoCallback : ( ) => void = ( ) => { } ;
138184
@@ -181,6 +227,16 @@ export default function ManageStructurePage() {
181227 await deleteSubject ( item . id ) ;
182228 } , `Subject '${ item . name } '` , undoCallback ) ;
183229
230+ } else if ( type === 'folder' ) {
231+ setFolders ( prev => prev . filter ( f => f . id !== item . id ) ) ;
232+ if ( selectedFolder ?. id === item . id ) setSelectedFolder ( null ) ;
233+ undoCallback = ( ) => {
234+ setFolders ( prev => [ ...prev , item ] . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ) ;
235+ } ;
236+ scheduleDelete ( item . id , async ( ) => {
237+ await deleteFolder ( item . id ) ;
238+ } , `Folder '${ item . name } '` , undoCallback ) ;
239+
184240 } else if ( type === 'note' ) {
185241 setNotes ( prev => prev . filter ( n => n . id !== item . id ) ) ;
186242 undoCallback = ( ) => {
@@ -212,7 +268,7 @@ export default function ManageStructurePage() {
212268 { editingItem && (
213269 < div style = { { position : "fixed" , top : 0 , left : 0 , right : 0 , bottom : 0 , background : "rgba(0,0,0,0.5)" , display : "flex" , alignItems : "center" , justifyContent : "center" , zIndex : 1000 } } >
214270 < div className = "card" style = { { width : "400px" } } >
215- < h3 > Edit { editingItem . type === 'dept' ? 'Department' : editingItem . type === 'batch' ? 'Batch' : editingItem . type === 'sem' ? 'Semester' : ' Subject'} </ h3 >
271+ < h3 > Edit { editingItem . type === 'dept' ? 'Department' : editingItem . type === 'batch' ? 'Batch' : editingItem . type === 'sem' ? 'Semester' : editingItem . type === 'sub' ? ' Subject' : 'Folder '} </ h3 >
216272 < input
217273 value = { editName } onChange = { e => setEditName ( e . target . value ) }
218274 className = "form-input" style = { { width : "100%" , padding : "0.5rem" , marginTop : "1rem" , marginBottom : "1rem" , borderRadius : "var(--radius)" , border : "1px solid var(--border)" } }
@@ -242,7 +298,7 @@ export default function ManageStructurePage() {
242298 padding : "0.5rem" , borderRadius : "var(--radius)" , marginBottom : "0.25rem" , cursor : "pointer" ,
243299 background : selectedDept ?. id === d . id ? "var(--primary)" : "transparent" , color : selectedDept ?. id === d . id ? "white" : "inherit" ,
244300 display : "flex" , justifyContent : "space-between" , alignItems : "center"
245- } } onClick = { ( ) => { setSelectedDept ( d ) ; setSelectedSem ( null ) ; setSelectedSub ( null ) ; } } >
301+ } } onClick = { ( ) => { setSelectedDept ( d ) ; setSelectedSem ( null ) ; setSelectedSub ( null ) ; setSelectedFolder ( null ) ; } } >
246302 < span > { d . name } </ span >
247303 { selectedDept ?. id !== d . id && (
248304 < div style = { { display : "flex" , gap : "0.5rem" } } >
@@ -274,7 +330,7 @@ export default function ManageStructurePage() {
274330 padding : "0.5rem" , borderRadius : "var(--radius)" , marginBottom : "0.25rem" , cursor : "pointer" ,
275331 background : selectedBatch ?. id === b . id ? "var(--primary)" : "transparent" , color : selectedBatch ?. id === b . id ? "white" : "inherit" ,
276332 display : "flex" , justifyContent : "space-between" , alignItems : "center"
277- } } onClick = { ( ) => { setSelectedBatch ( b ) ; setSelectedSem ( null ) ; setSelectedSub ( null ) ; } } >
333+ } } onClick = { ( ) => { setSelectedBatch ( b ) ; setSelectedSem ( null ) ; setSelectedSub ( null ) ; setSelectedFolder ( null ) ; } } >
278334 < span > { b . name } </ span >
279335 { selectedBatch ?. id !== b . id && (
280336 < div style = { { display : "flex" , gap : "0.5rem" } } >
@@ -307,7 +363,7 @@ export default function ManageStructurePage() {
307363 padding : "0.5rem" , borderRadius : "var(--radius)" , marginBottom : "0.25rem" , cursor : "pointer" ,
308364 background : selectedSem ?. id === s . id ? "var(--primary)" : "transparent" , color : selectedSem ?. id === s . id ? "white" : "inherit" ,
309365 display : "flex" , justifyContent : "space-between" , alignItems : "center"
310- } } onClick = { ( ) => { setSelectedSem ( s ) ; setSelectedSub ( null ) ; } } >
366+ } } onClick = { ( ) => { setSelectedSem ( s ) ; setSelectedSub ( null ) ; setSelectedFolder ( null ) ; } } >
311367 < span > { s . name } </ span >
312368 { selectedSem ?. id !== s . id && (
313369 < div style = { { display : "flex" , gap : "0.5rem" } } >
@@ -353,15 +409,68 @@ export default function ManageStructurePage() {
353409 </ ul >
354410 </ div >
355411 ) }
356-
357- { /* 4. Notes (Drill-down final level) */ }
412+ { /* 4. Folders */ }
358413 { selectedSub && (
359414 < div className = "card" >
360415 < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "1rem" , borderBottom : "1px solid var(--border)" , paddingBottom : "0.5rem" } } >
361- < h3 > Notes </ h3 >
416+ < h3 > Folders </ h3 >
362417 < button onClick = { ( ) => setSelectedSub ( null ) } style = { { background : "none" , border : "none" , cursor : "pointer" } } > < X size = { 16 } /> </ button >
363418 </ div >
364- < p style = { { fontSize : "0.9rem" , color : "var(--text-muted)" , marginBottom : "1rem" } } > Managing notes for < strong > { selectedSub . name } </ strong > </ p >
419+ < div style = { { display : "flex" , gap : "0.5rem" , marginBottom : "1rem" } } >
420+ < input className = "form-input" placeholder = "New Folder..."
421+ value = { newFolderName } onChange = { e => setNewFolderName ( e . target . value ) }
422+ style = { { flex : 1 , padding : "0.5rem" , borderRadius : "var(--radius)" , border : "1px solid var(--border)" } } />
423+ < button className = "btn btn-primary" onClick = { handleCreateFolder } disabled = { ! newFolderName . trim ( ) || isCreating } style = { { padding : "0.5rem" } } > < Plus size = { 20 } /> </ button >
424+ </ div >
425+ < ul style = { { listStyle : "none" } } >
426+ < li
427+ style = { {
428+ padding : "0.5rem" , borderRadius : "var(--radius)" , marginBottom : "0.25rem" , cursor : "pointer" ,
429+ background : ! selectedFolder ? "var(--primary)" : "transparent" , color : ! selectedFolder ? "white" : "inherit" ,
430+ display : "flex" , justifyContent : "space-between" , alignItems : "center"
431+ } }
432+ onClick = { ( ) => setSelectedFolder ( null ) }
433+ >
434+ < span > General Notes / All</ span >
435+ </ li >
436+ { folders . map ( f => (
437+ < li key = { f . id } style = { {
438+ padding : "0.5rem" , borderRadius : "var(--radius)" , marginBottom : "0.25rem" , cursor : "pointer" ,
439+ background : selectedFolder ?. id === f . id ? "var(--primary)" : "transparent" , color : selectedFolder ?. id === f . id ? "white" : "inherit" ,
440+ display : "flex" , justifyContent : "space-between" , alignItems : "center"
441+ } } onClick = { ( ) => setSelectedFolder ( f ) } >
442+ < span > { f . name } </ span >
443+ < div style = { { display : "flex" , gap : "0.5rem" } } >
444+ < button onClick = { ( e ) => { e . stopPropagation ( ) ; startEdit ( f , 'folder' ) ; } } style = { { background : "none" , border : "none" , color : "inherit" , cursor : "pointer" } } > < Pencil size = { 14 } /> </ button >
445+ < button onClick = { ( e ) => { e . stopPropagation ( ) ; handleDelete ( f , 'folder' ) ; } } style = { { background : "none" , border : "none" , color : "inherit" , cursor : "pointer" } } > < Trash2 size = { 14 } /> </ button >
446+ </ div >
447+ </ li >
448+ ) ) }
449+ </ ul >
450+ </ div >
451+ ) }
452+
453+ { /* 5. Notes (Filtered by Folder) */ }
454+ { selectedSub && (
455+ < div className = "card" >
456+ < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "1rem" , borderBottom : "1px solid var(--border)" , paddingBottom : "0.5rem" } } >
457+ < h3 >
458+ { selectedFolder ? `Notes in ${ selectedFolder . name } ` : 'General Notes' }
459+ </ h3 >
460+ { selectedFolder && (
461+ < div style = { { display : "flex" , gap : "0.5rem" } } >
462+ < button onClick = { ( ) => startEdit ( selectedFolder , 'folder' ) } className = "btn btn-outline" style = { { padding : "0.25rem 0.5rem" , fontSize : "0.8rem" , height : "auto" } } >
463+ < Pencil size = { 14 } /> Rename Folder
464+ </ button >
465+ < button onClick = { ( ) => handleDelete ( selectedFolder , 'folder' ) } className = "btn btn-outline" style = { { padding : "0.25rem 0.5rem" , fontSize : "0.8rem" , height : "auto" , color : "var(--danger)" , borderColor : "var(--danger)" } } >
466+ < Trash2 size = { 14 } /> Delete Folder
467+ </ button >
468+ </ div >
469+ ) }
470+ </ div >
471+ < p style = { { fontSize : "0.9rem" , color : "var(--text-muted)" , marginBottom : "1rem" } } >
472+ Managing notes for < strong > { selectedSub . name } </ strong > { selectedFolder ? ` > ${ selectedFolder . name } ` : '' }
473+ </ p >
365474
366475 < ul style = { { listStyle : "none" } } >
367476 { notes . length === 0 && < li style = { { color : "var(--text-muted)" , fontStyle : "italic" } } > No notes here. Use Dashboard to upload.</ li > }
0 commit comments