Skip to content

Commit 181cbd3

Browse files
committed
feat: Upload flow improvements, dark mode fixes, admin folder CRUD, and config revert
1 parent b8b1e39 commit 181cbd3

6 files changed

Lines changed: 575 additions & 158 deletions

File tree

src/app/dashboard/manage/page.tsx

Lines changed: 127 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
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";
1112
import { 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

Comments
 (0)