Remove notes and deployment alert recipients from Admin console and improve Telegram Senior Responders persistence
This commit is contained in:
@@ -469,20 +469,8 @@ export default function AdminDashboard() {
|
|||||||
try { localConfig = JSON.parse(saved); } catch {}
|
try { localConfig = JSON.parse(saved); } catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(getBackendUrl('/api/admin/telegram/config'))
|
if (!localConfig || (!localConfig.seniorHelpers?.length && !localConfig.helper_chat_ids?.length)) {
|
||||||
.then((res) => {
|
localConfig = {
|
||||||
if (!res.ok) throw new Error('Failed to fetch from backend');
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
setTelegramConfig(data);
|
|
||||||
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(data));
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (localConfig) {
|
|
||||||
setTelegramConfig(localConfig);
|
|
||||||
} else {
|
|
||||||
const defaultConfig: TelegramConfig = {
|
|
||||||
community_bot_token: '7829148291:AAH_rit_community_bot_token_sample',
|
community_bot_token: '7829148291:AAH_rit_community_bot_token_sample',
|
||||||
spring_backend_url: 'https://rit-services.in/api',
|
spring_backend_url: 'https://rit-services.in/api',
|
||||||
helper_chat_ids: [971749136, 982314512],
|
helper_chat_ids: [971749136, 982314512],
|
||||||
@@ -491,10 +479,29 @@ export default function AdminDashboard() {
|
|||||||
{ chatId: 982314512, name: 'Sneha (ECE 3rd Yr)' }
|
{ chatId: 982314512, name: 'Sneha (ECE 3rd Yr)' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
setTelegramConfig(defaultConfig);
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(localConfig));
|
||||||
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(defaultConfig));
|
}
|
||||||
|
|
||||||
|
setTelegramConfig(localConfig);
|
||||||
|
|
||||||
|
fetch(getBackendUrl('/api/admin/telegram/config'))
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) throw new Error('Failed to fetch from backend');
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
const merged: TelegramConfig = {
|
||||||
|
community_bot_token: data.community_bot_token || localConfig?.community_bot_token,
|
||||||
|
spring_backend_url: data.spring_backend_url || localConfig?.spring_backend_url,
|
||||||
|
helper_chat_ids: (data.helper_chat_ids && data.helper_chat_ids.length > 0) ? data.helper_chat_ids : localConfig?.helper_chat_ids,
|
||||||
|
seniorHelpers: (data.seniorHelpers && data.seniorHelpers.length > 0) ? data.seniorHelpers : localConfig?.seniorHelpers
|
||||||
|
};
|
||||||
|
setTelegramConfig(merged);
|
||||||
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, JSON.stringify(merged));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
.finally(() => setLoadingTelegram(false));
|
.finally(() => setLoadingTelegram(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -571,171 +578,7 @@ export default function AdminDashboard() {
|
|||||||
.finally(() => setSavingBotConfig(false));
|
.finally(() => setSavingBotConfig(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// NOTES & STUDY MATERIALS
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const fetchNotes = () => {
|
|
||||||
setLoadingNotes(true);
|
|
||||||
const saved = localStorage.getItem(NOTES_STORAGE_KEY);
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved);
|
|
||||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
||||||
setNotes(parsed);
|
|
||||||
setLoadingNotes(false);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(getBackendUrl('/api/admin/notes'))
|
|
||||||
.then((res) => {
|
|
||||||
if (!res.ok) throw new Error();
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
setNotes(data);
|
|
||||||
localStorage.setItem(NOTES_STORAGE_KEY, JSON.stringify(data));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!saved) {
|
|
||||||
const initialNotes: NoteItem[] = [
|
|
||||||
{ id: 1, title: 'Data Structures Unit 1 Notes', subject: 'CS3301', authorName: 'Dr. ARTHI A.', department: 'AI&DS', fileUrl: '#', semester: 3, uploadDate: new Date().toISOString() },
|
|
||||||
{ id: 2, title: 'Matrices PYQ 2023', subject: 'MA3151', authorName: 'Prof. Senthil', department: 'S&H', fileUrl: '#', semester: 1, uploadDate: new Date(Date.now() - 86400000).toISOString() }
|
|
||||||
];
|
|
||||||
setNotes(initialNotes);
|
|
||||||
localStorage.setItem(NOTES_STORAGE_KEY, JSON.stringify(initialNotes));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => setLoadingNotes(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteNote = (id: number, title: string) => {
|
|
||||||
if (!confirm(`Delete study note "${title}"?`)) return;
|
|
||||||
const updated = notes.filter((n) => n.id !== id);
|
|
||||||
setNotes(updated);
|
|
||||||
localStorage.setItem(NOTES_STORAGE_KEY, JSON.stringify(updated));
|
|
||||||
showToast('success', `Study note "${title}" deleted.`);
|
|
||||||
|
|
||||||
fetch(getBackendUrl(`/api/admin/notes/${id}`), { method: 'DELETE' }).catch(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// COMMUNITY QUESTIONS
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const fetchQuestions = () => {
|
|
||||||
setLoadingQuestions(true);
|
|
||||||
const saved = localStorage.getItem(QUESTIONS_STORAGE_KEY);
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved);
|
|
||||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
||||||
setQuestions(parsed);
|
|
||||||
setLoadingQuestions(false);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(getBackendUrl('/api/admin/questions'))
|
|
||||||
.then((res) => {
|
|
||||||
if (!res.ok) throw new Error();
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
setQuestions(data);
|
|
||||||
localStorage.setItem(QUESTIONS_STORAGE_KEY, JSON.stringify(data));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!saved) {
|
|
||||||
const initialQuestions: QuestionItem[] = [
|
|
||||||
{ id: 1, title: "When is the fresher's orientation?", content: "I couldn't find the exact date for the CSE orientation.", authorName: "Karthik", authorEmail: "karthik.2024@ritchennai.edu.in", createdAt: new Date().toISOString(), status: "PENDING", tags: ["orientation"], votes: 0 },
|
|
||||||
{ id: 2, title: "Are laptops mandatory in first year?", content: "Just wondering if we need to bring laptops to college every day.", authorName: "Sneha", authorEmail: "sneha.2024@ritchennai.edu.in", createdAt: new Date(Date.now() - 86400000).toISOString(), status: "APPROVED", tags: ["academics"], votes: 5 }
|
|
||||||
];
|
|
||||||
setQuestions(initialQuestions);
|
|
||||||
localStorage.setItem(QUESTIONS_STORAGE_KEY, JSON.stringify(initialQuestions));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => setLoadingQuestions(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteQuestion = (id: number, title: string) => {
|
|
||||||
if (!confirm(`Delete student question "${title}"?`)) return;
|
|
||||||
const updated = questions.filter((q) => q.id !== id);
|
|
||||||
setQuestions(updated);
|
|
||||||
localStorage.setItem(QUESTIONS_STORAGE_KEY, JSON.stringify(updated));
|
|
||||||
showToast('success', `Question "${title}" deleted.`);
|
|
||||||
|
|
||||||
fetch(getBackendUrl(`/api/admin/questions/${id}`), { method: 'DELETE' }).catch(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// DEPLOYMENT SUBSCRIBERS
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
const fetchRecipients = () => {
|
|
||||||
setLoadingRecipients(true);
|
|
||||||
const saved = localStorage.getItem(RECIPIENTS_STORAGE_KEY);
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved);
|
|
||||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
||||||
setRecipients(parsed);
|
|
||||||
setLoadingRecipients(false);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(getBackendUrl('/api/admin/recipients'))
|
|
||||||
.then((res) => {
|
|
||||||
if (!res.ok) throw new Error();
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
setRecipients(data);
|
|
||||||
localStorage.setItem(RECIPIENTS_STORAGE_KEY, JSON.stringify(data));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!saved) {
|
|
||||||
const initialRecipients = ['devops@ritchennai.edu.in', 'admin@ritchennai.edu.in'];
|
|
||||||
setRecipients(initialRecipients);
|
|
||||||
localStorage.setItem(RECIPIENTS_STORAGE_KEY, JSON.stringify(initialRecipients));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => setLoadingRecipients(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddRecipient = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!newEmail || !newEmail.includes('@')) {
|
|
||||||
showToast('error', 'Please enter a valid email address');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const updated = [...recipients, newEmail.trim()];
|
|
||||||
setRecipients(updated);
|
|
||||||
localStorage.setItem(RECIPIENTS_STORAGE_KEY, JSON.stringify(updated));
|
|
||||||
setNewEmail('');
|
|
||||||
showToast('success', `Recipient ${newEmail.trim()} added!`);
|
|
||||||
|
|
||||||
fetch(getBackendUrl('/api/admin/recipients'), {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ email: newEmail.trim() }),
|
|
||||||
}).catch(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveRecipient = (email: string) => {
|
|
||||||
if (!confirm(`Remove ${email} from deployment notifications?`)) return;
|
|
||||||
const updated = recipients.filter((r) => r !== email);
|
|
||||||
setRecipients(updated);
|
|
||||||
localStorage.setItem(RECIPIENTS_STORAGE_KEY, JSON.stringify(updated));
|
|
||||||
showToast('success', `Recipient ${email} removed.`);
|
|
||||||
|
|
||||||
fetch(getBackendUrl(`/api/admin/recipients?email=${encodeURIComponent(email)}`), { method: 'DELETE' }).catch(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// FACULTY MANAGEMENT HANDLERS
|
// FACULTY MANAGEMENT HANDLERS
|
||||||
@@ -1171,9 +1014,7 @@ export default function AdminDashboard() {
|
|||||||
{[
|
{[
|
||||||
{ id: 'transport', label: 'Bus Fleet & Routes', icon: Bus },
|
{ id: 'transport', label: 'Bus Fleet & Routes', icon: Bus },
|
||||||
{ id: 'telegram', label: 'Telegram Q&A Seniors', icon: Bot },
|
{ id: 'telegram', label: 'Telegram Q&A Seniors', icon: Bot },
|
||||||
{ id: 'notes', label: 'Notes & Study Materials', icon: BookOpen },
|
|
||||||
{ id: 'community', label: 'Community Q&A Moderation', icon: MessageCircle },
|
{ id: 'community', label: 'Community Q&A Moderation', icon: MessageCircle },
|
||||||
{ id: 'subscribers', label: 'Deployment Email Alerts', icon: Mail },
|
|
||||||
{ id: 'faculty', label: 'Faculty Directory', icon: UserCheck },
|
{ id: 'faculty', label: 'Faculty Directory', icon: UserCheck },
|
||||||
{ id: 'clubs', label: 'Clubs & Centers', icon: Building2 },
|
{ id: 'clubs', label: 'Clubs & Centers', icon: Building2 },
|
||||||
{ id: 'curriculum', label: 'GPA Curriculum & Credits', icon: Calculator },
|
{ id: 'curriculum', label: 'GPA Curriculum & Credits', icon: Calculator },
|
||||||
@@ -1629,72 +1470,7 @@ export default function AdminDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ───────────────────────────────────────────────────────────── */}
|
|
||||||
{/* TAB 5: DEPLOYMENT EMAIL SUBSCRIBERS */}
|
|
||||||
{/* ───────────────────────────────────────────────────────────── */}
|
|
||||||
{activeTab === 'subscribers' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="bg-slate-900/80 border border-white/10 rounded-3xl p-6 shadow-xl space-y-4">
|
|
||||||
<h3 className="text-base font-bold text-white flex items-center gap-2">
|
|
||||||
<Mail className="w-5 h-5 text-orange-400" />
|
|
||||||
Add Deployment Alert Recipient
|
|
||||||
</h3>
|
|
||||||
<p className="text-xs text-slate-400">
|
|
||||||
Every <code className="text-orange-400 font-mono">git push</code> automatically sends live health reports to all emails listed below.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form onSubmit={handleAddRecipient} className="flex flex-col sm:flex-row gap-3">
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<Mail className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
value={newEmail}
|
|
||||||
onChange={(e) => setNewEmail(e.target.value)}
|
|
||||||
placeholder="e.g. admin@ritchennai.edu.in"
|
|
||||||
className="w-full pl-10 pr-4 py-3 rounded-xl bg-slate-950 border border-white/10 text-xs text-white placeholder-slate-500 outline-none focus:border-orange-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={submittingEmail}
|
|
||||||
className="px-6 py-3 rounded-xl bg-orange-500 hover:bg-orange-600 text-white text-xs font-bold shadow-lg shadow-orange-500/20 transition-all cursor-pointer disabled:opacity-50 whitespace-nowrap"
|
|
||||||
>
|
|
||||||
{submittingEmail ? 'Adding...' : '➕ Add Subscriber'}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-slate-900/80 border border-white/10 rounded-3xl p-6 shadow-xl space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h3 className="text-sm font-bold text-white">Active Email Subscribers ({recipients.length})</h3>
|
|
||||||
<button onClick={fetchRecipients} className="p-2 rounded-lg text-slate-400 hover:text-white">
|
|
||||||
<RefreshCw className={`w-3.5 h-3.5 ${loadingRecipients ? 'animate-spin' : ''}`} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{recipients.map((email, idx) => (
|
|
||||||
<div key={idx} className="bg-slate-950/60 border border-white/10 rounded-2xl p-4 flex items-center justify-between gap-4">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="w-8 h-8 rounded-xl bg-orange-500/10 border border-orange-500/20 flex items-center justify-center shrink-0">
|
|
||||||
<Send className="w-3.5 h-3.5 text-orange-400" />
|
|
||||||
</div>
|
|
||||||
<p className="text-xs font-mono font-bold text-white">{email}</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => handleRemoveRecipient(email)}
|
|
||||||
className="px-3 py-1.5 rounded-xl bg-rose-500/10 hover:bg-rose-500 text-rose-400 hover:text-white text-xs font-semibold transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
|
||||||
<span>Remove</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ───────────────────────────────────────────────────────────── */}
|
{/* ───────────────────────────────────────────────────────────── */}
|
||||||
{/* TAB 6: FACULTY DIRECTORY MANAGEMENT */}
|
{/* TAB 6: FACULTY DIRECTORY MANAGEMENT */}
|
||||||
|
|||||||
Reference in New Issue
Block a user