Your private operations hub. Enter your password to continue.
Wrong password.
NEXTGENZ CENTRE Operations Hub
Partners · Students · Marketing — all in one place
Needs Attention
Loading...
Name
Type
Location
Priority
Status
Last Contact
Needs Follow-Up
Loading...
Student
Agent
University
Country
Stage
Intake
Add Partner
Type an email and press Enter (or paste a comma-separated list). Add as many as you need.
Documents (Google Drive links)
Paste a Google Drive share link (set to "Anyone with the link"). Click the document name any time to review or download it right here — no need to leave the Hub.
Manage these files from the "Attachments" button in the header.
Copied to clipboard.
Email sent successfully!
Use “Mark as Sent” if you manually sent via email app and just want to log it.
Default Attachments
Upload these once — they'll be offered as tick-boxes on every email you draft, so you don't need to attach them by hand each time.
No file saved yet.
No file saved yet.
Daily Marketing Command Center
—0 / 4 today
A fresh, platform-specific move every day — never the same task twice in a row. Tick it off once it's posted.
Browser reminders only fire while this app is open in a tab. For a true daily nudge even when it's closed, see the automated email brief setup note at the bottom of this page.
Today's Call Time Planner (MYT)
Highlighted row = best region to call right now, based on your local time.
Time (MYT)
Target Region
Countries Waking Up
Strategy
Weekly Content Calendar
Rotate template A/B/C. Never change logo position, WhatsApp bar, or font — only background image and text.
Daily Production Workflow
1. Morning Order (10 min)
Open your AI image tool. Paste today's prompt from the calendar above to get text + image idea.
2. Factory Production (10 min)
Generate the image, drop into your editor, add text + logo (top left, fixed position).
3. Distribution (5 min)
Post to Instagram → share to Facebook → TikTok (photo mode + trending song) → Telegram channel.
4. Harvest (all day)
Only reply to WhatsApp link clicks or specific keyword DMs (e.g. "VISA"). Ignore vague comments.
Platform Playbooks
Each platform converts differently. Don't post the same thing everywhere — this is the strategy behind each one.
Today's Carousel Campaign (Students)
Always a 3-slide carousel — Hook → Value → CTA, 4:5, in NEXTGENZ CENTRE's brand colors — auto-written from your partner list. Partner names are never used, only category + city.
Caption (posts alongside the carousel)
Audio Recommendation
How to Recruit Agents
Recruiting sub-agents is a different audience than recruiting students — you're pitching to consultants and agencies, not families. Different channels, different message.
LinkedIn
Post the recruitment pitch as a normal post + directly message education consultants in your target countries. Best for credibility — agents check a company's LinkedIn before replying.
Facebook Groups
Search "[Country] education agents" / "study abroad consultants [Country]" groups and post there directly — this is where most independent agents actually hang out.
WhatsApp (direct)
Highest reply rate. Use the "Draft WhatsApp Message" button in each Agent's profile to send the pitch straight to their number, pre-filled and ready to send.
Today's Recruitment Carousel
Post caption
Audio Recommendation
Each carousel is 3 slides — Hook, Value, then a WhatsApp-focused CTA — always in NEXTGENZ CENTRE's navy & gold brand colors, with the logo corner left clean for you to drop your logo in during editing.
True Daily Auto-Reminder (no app needed)
The "Email Me Today's Brief" button above sends instantly, but only when you tap it. To have this land in your inbox automatically every morning — even if you never open the Hub — a scheduled function file (netlify/functions/marketing-daily-brief.js) has been added to your project. Deploy it to Netlify with your existing RESEND_API_KEY env var already set, and it will email your daily platform tasks every morning at 9:00 AM Malaysia time automatically.
TikToker Recruitment Message
Generic: no name, paste straight into the DM
Edits are saved in this browser. Every template mentions that NextGenZ Centre is SSM-registered.
Needs Attention
Loading...
"Add Target Countries" seeds one row per country from your representatives list (skips ones you already have) so you can track sub-agent recruitment market by market. Every new row defaults to 30% commission — edit per-agent if you negotiate differently.
Country
Agency / Contact
Priority
Commission
Status
Last Contact
Add Agent
Type an email and press Enter (or paste a comma-separated list).
Follow-up Log
All changes saved
fr.scrollIntoView({ block: 'start', behavior: 'smooth' }), 50);
}
if (tab === 'feereply') {
const fr = document.getElementById('feeReplyFrame');
if (!fr.dataset.loaded) {
fr.srcdoc = document.getElementById('feeReplySource').textContent.replace(/<\\\/script/g, ' fr.scrollIntoView({ block: 'start', behavior: 'smooth' }), 50);
}
const addBtn = document.getElementById('addBtn');
const addLabel = document.getElementById('addBtnLabel');
if (tab === 'partners') { addBtn.classList.remove('hidden'); addLabel.textContent = 'Add Partner'; }
else if (tab === 'students') { addBtn.classList.remove('hidden'); addLabel.textContent = 'Add Student'; }
else if (tab === 'agents') { addBtn.classList.remove('hidden'); addLabel.textContent = 'Add Agent'; }
else { addBtn.classList.add('hidden'); }
}
function handleAddClick() {
if (activeTab === 'partners') openPartnerModal();
else if (activeTab === 'students') openStudentModal();
else if (activeTab === 'agents') openAgentModal();
}
// ---------- EMAIL TEMPLATES ----------
const TEMPLATES = {
University: {
subject: 'Partnership Inquiry - Student Recruitment Agent - NEXTGENZ CENTRE',
body: `Dear {{name}} Global Partnership Team,
I hope this email finds you well. We are writing to you from NEXTGENZ CENTRE (202603180524), an education consultancy based in Malaysia. I am sending this communication in my capacity as Manager to formally express our interest in becoming an authorized Student Recruitment Agent for {{name}}.
We have long admired {{name}}'s reputation and its diverse international community.
Our Market Focus: At NEXTGENZ CENTRE, we specialize in recruiting high-quality international students from Indonesia and the MENA region (Middle East & North Africa). We currently have a strong pool of prospective students from these regions looking for suitable programs, making {{name}} their ideal destination.
Our Request: We are eager to establish a formal partnership to begin enrolling students at {{name}} for upcoming intakes. Could you please guide us on the following:
1- The official Agent Application procedure.
2- The required documents needed to register our agency.
3- Your current commission structure for international students.
We are ready to start promoting {{name}}'s programs to our network immediately. Thank you for your time and consideration.
Sincerely,
RABIATUL ADAWIYAH BT ROSDI
MANAGER
NEXTGENZ CENTRE
Email: ${CONFIG.SENDER_EMAIL}
Website: ${CONFIG.WEBSITE}
WhatsApp: https://wa.me/${CONFIG.CONTACT_WHATSAPP}`
},
'English Centre': {
subject: 'Partnership Inquiry - Student Recruitment Agent - NEXTGENZ CENTRE',
body: `Dear Admissions & Marketing Team,
I hope this email finds you well. We are writing to you on behalf of NEXTGENZ CENTRE (SSM: 202603180524). We would like to formally express our interest in becoming an authorized student recruitment agent for {{name}}.
We are currently expanding our recruitment efforts for the 2026 academic intakes, with a specific focus on students from North Africa and the Middle East. We have identified {{name}} as a top preference for our students due to your reputation and quality of education.
We would like to request the following:
1- The official Agent Application Form.
2- Details regarding your commission structure and agent terms.
3- Any marketing materials (brochures/fee structures/videos) we can use to promote your courses immediately.
We are based in Malaysia and are ready to start recruiting students for you immediately. We are available for a meeting or a call at your earliest convenience to discuss this potential partnership.
Thank you for your time and consideration.
Best regards,
RABIATUL ADAWIYAH BT ROSDI
MANAGER
NEXTGENZ CENTRE
Email: ${CONFIG.SENDER_EMAIL}
Website: ${CONFIG.WEBSITE}
WhatsApp: https://wa.me/${CONFIG.CONTACT_WHATSAPP}`
}
};
// ---------- AGENT RECRUITMENT EMAIL TEMPLATE ----------
// Used when NEXTGENZ CENTRE reaches out to a prospective overseas sub-agent, offering
// a 30% commission to represent us and recruit students in their country.
const AGENT_TEMPLATE = {
subject: 'Become Our Official Student Recruitment Partner in {{country}} - NEXTGENZ CENTRE (30% Commission)',
body: `Dear {{name}},
I hope this message finds you well. My name is Rabiatul Adawiyah Bt Rosdi, Manager of NEXTGENZ CENTRE (SSM: 202603180524), an education consultancy based in Kuala Lumpur, Malaysia specializing in placing international students into Malaysian universities and English language centres.
We are expanding our representative network in {{country}} and would like to invite you to become our official recruitment partner for your market.
What we offer:
1- 30% commission on every successfully enrolled student you refer to us.
2- Full support with university selection, application processing, offer letters, and visa guidance for your students.
3- Marketing materials, program brochures, and direct access to our partner universities and English centres in Malaysia.
4- A dedicated point of contact on our team for fast turnaround on your applications.
What we're looking for:
1- Your interest and availability to represent NEXTGENZ CENTRE in {{country}}.
2- A brief introduction to your current student network or recruitment channels.
3- Any questions you have about our process, commission payment terms, or partner requirements.
We are ready to onboard you immediately and begin sending student profiles for Malaysian universities and English programs. Please let us know a convenient time for a call, or simply reply to this email to get started.
Looking forward to a successful partnership.
Best regards,
RABIATUL ADAWIYAH BT ROSDI
MANAGER
NEXTGENZ CENTRE
Email: ${CONFIG.SENDER_EMAIL}
Website: ${CONFIG.WEBSITE}
WhatsApp: https://wa.me/${CONFIG.CONTACT_WHATSAPP}`
};
// ---------- FOLLOW-UP EMAIL TEMPLATES ----------
// Two branches: they replied before (reference what they said) vs. silence (a polite bump).
const FOLLOWUP_TEMPLATES = {
hadResponse: {
subject: 'Re: Partnership Inquiry - Next Steps - NEXTGENZ CENTRE',
body: `Dear {{name}} Team,
Thank you for getting back to us regarding our partnership request. {{previousReply}}
Based on what you've shared, we'd like to move this forward without delay. Could you please confirm:
1- The next step to finalize our agent status on your end.
2- Any outstanding documents or agreements you need from us.
We currently have students from Indonesia and the MENA region ready to apply once we're set up as your registered partner, so we're keen to move quickly.
Looking forward to your update.
Best regards,
RABIATUL ADAWIYAH BT ROSDI
MANAGER
NEXTGENZ CENTRE
Email: ${CONFIG.SENDER_EMAIL}
Website: ${CONFIG.WEBSITE}
WhatsApp: https://wa.me/${CONFIG.CONTACT_WHATSAPP}`
},
noResponse: {
subject: 'Following Up - Partnership Inquiry - NEXTGENZ CENTRE',
body: `Dear {{name}} Team,
I hope you're doing well. I'm following up on my email sent on {{dateSent}} ({{daysSince}} days ago) regarding a Student Recruitment Agent partnership between NEXTGENZ CENTRE and {{name}}.
I understand inboxes get busy, so I wanted to check back in case it was missed. We currently have active students from Indonesia and the MENA region ready to apply, and we'd love to get the process started with your team as soon as possible.
Could you kindly let us know:
1- If you need any additional information from our side.
2- The right contact person to move this forward.
Thank you, and I look forward to hearing from you.
Best regards,
RABIATUL ADAWIYAH BT ROSDI
MANAGER
NEXTGENZ CENTRE
Email: ${CONFIG.SENDER_EMAIL}
Website: ${CONFIG.WEBSITE}
WhatsApp: https://wa.me/${CONFIG.CONTACT_WHATSAPP}`
}
};
// Draft a follow-up email. Asks whether the school/centre already replied:
// - Yes -> ask what they said, and the email references it and pushes for next steps.
// - No -> a polite bump email referencing how long it's been since the first email.
function draftFollowUpFor(id) {
if (!id) { showToast('Save this partner first, then use Follow Up.'); return; }
const p = allPartners.find(x => x.id === id);
if (!p) { showToast('Partner not found.'); return; }
const hadReply = confirm(`Did ${p.name} reply to your previous email?\n\nOK = Yes, they replied\nCancel = No answer yet`);
let subject, body;
if (hadReply) {
const lastNote = (p.notes && p.notes.length) ? p.notes[p.notes.length - 1].text : '';
const suggested = (lastNote && !/^Email sent|^Follow-up email sent/.test(lastNote)) ? lastNote : '';
const previousReply = prompt('Briefly, what did they say / what stage are things at?', suggested);
if (previousReply === null) return; // user cancelled the prompt
subject = FOLLOWUP_TEMPLATES.hadResponse.subject.replace(/\{\{name\}\}/g, p.name);
body = FOLLOWUP_TEMPLATES.hadResponse.body
.replace(/\{\{name\}\}/g, p.name)
.replace(/\{\{previousReply\}\}/g, previousReply.trim() || 'You mentioned some details about the process.');
} else {
const daysSince = p.dateSent ? Math.max(0, Math.round((new Date() - new Date(p.dateSent)) / 86400000)) : null;
subject = FOLLOWUP_TEMPLATES.noResponse.subject.replace(/\{\{name\}\}/g, p.name);
body = FOLLOWUP_TEMPLATES.noResponse.body
.replace(/\{\{name\}\}/g, p.name)
.replace(/\{\{daysSince\}\}/g, daysSince === null ? 'a few' : String(daysSince))
.replace(/\{\{dateSent\}\}/g, p.dateSent || 'recently');
}
const emailListFU = splitEmails(p.email);
document.getElementById('em_to').value = emailListFU[0] || '';
setChipValues('em_cc_chips', [CONFIG.ADMIN_EMAIL, ...emailListFU.slice(1)].filter(Boolean));
document.getElementById('em_subject').value = subject;
document.getElementById('em_body').value = body;
document.getElementById('emailModal').dataset.partnerId = id;
document.getElementById('emailModal').dataset.agentId = '';
document.getElementById('emailModal').dataset.followUp = hadReply ? 'reply' : 'noreply';
document.getElementById('em_copiedMsg').classList.add('hidden');
document.getElementById('em_sentMsg').classList.add('hidden');
document.getElementById('em_errMsg').classList.add('hidden');
refreshEmailAttachOptions();
showEl('emailModal');
}
// ---------- CHIP INPUT (multi-email fields: Contact Email(s), CC) ----------
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const chipInputState = {};
function splitEmails(str) {
return (str || '').split(/[\/,;\n]+/).map(s => s.trim()).filter(Boolean);
}
function renderChipInputContainer(containerId) {
const container = document.getElementById(containerId);
if (!container) return;
const values = chipInputState[containerId] || [];
container.innerHTML = '';
values.forEach((val, idx) => {
const chip = document.createElement('span');
chip.className = 'chip';
const label = document.createElement('span');
label.textContent = val;
chip.appendChild(label);
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'chip-remove';
removeBtn.innerHTML = '×';
removeBtn.onclick = (e) => { e.stopPropagation(); removeChip(containerId, idx); };
chip.appendChild(removeBtn);
container.appendChild(chip);
});
const input = document.createElement('input');
input.type = 'text';
input.className = 'chip-input-text';
input.placeholder = values.length ? '' : 'name@example.com';
input.addEventListener('keydown', (e) => handleChipInputKeydown(e, containerId));
input.addEventListener('blur', () => commitChipInputText(containerId));
input.addEventListener('click', (e) => e.stopPropagation());
input.addEventListener('paste', () => setTimeout(() => commitChipInputText(containerId), 0));
container.appendChild(input);
}
function focusChipInput(containerId) {
const container = document.getElementById(containerId);
const input = container && container.querySelector('.chip-input-text');
if (input) input.focus();
}
function handleChipInputKeydown(e, containerId) {
if (e.key === 'Enter' || e.key === ',' || e.key === ';' || e.key === 'Tab') {
if (e.target.value.trim()) {
e.preventDefault();
commitChipInputText(containerId);
}
} else if (e.key === 'Backspace' && !e.target.value) {
const values = chipInputState[containerId] || [];
if (values.length) removeChip(containerId, values.length - 1);
}
}
function commitChipInputText(containerId) {
const container = document.getElementById(containerId);
const input = container && container.querySelector('.chip-input-text');
if (!input || !input.value.trim()) { if (input) input.value = ''; return; }
const parts = splitEmails(input.value);
const values = chipInputState[containerId] || (chipInputState[containerId] = []);
parts.forEach(p => {
if (!EMAIL_RE.test(p)) { showToast('Skipped "' + p + '" - not a valid email.'); return; }
if (!values.includes(p)) values.push(p);
});
input.value = '';
renderChipInputContainer(containerId);
focusChipInput(containerId);
onChipInputChanged(containerId);
}
function removeChip(containerId, idx) {
const values = chipInputState[containerId] || [];
values.splice(idx, 1);
renderChipInputContainer(containerId);
onChipInputChanged(containerId);
}
function setChipValues(containerId, values) {
chipInputState[containerId] = (values || []).filter(Boolean);
renderChipInputContainer(containerId);
}
function getChipValues(containerId) {
const container = document.getElementById(containerId);
const input = container && container.querySelector('.chip-input-text');
if (input && input.value.trim()) commitChipInputText(containerId);
return (chipInputState[containerId] || []).slice();
}
function onChipInputChanged(containerId) {
if (containerId === 'pf_email_chips') schedulePartnerAutosave();
}
// ================= PARTNERS =================
let allPartners = [];
let currentEditId = null;
let selectedPartnerIds = new Set();
async function loadPartners() {
try {
const snap = await db.collection('partners').get();
allPartners = snap.docs.map(d => ({ id: d.id, ...d.data() }));
document.getElementById('importBtn').classList.toggle('hidden', allPartners.length > 0);
renderStats();
renderAttention();
renderTable();
} catch (err) {
console.error(err);
document.getElementById('partnersTableBody').innerHTML = '
Could not load data. Check your connection. ' + String((err && (err.code || err.message)) || err).replace(/[<>&]/g,'') + (location.protocol !== 'https:' ? ' — this file is opened from your phone, not from your website. Open it from nextgenzcentre.space.' : '') + '
`).join('');
}
function addNoteToForm() {
const input = document.getElementById('pf_newNote');
if (!input.value.trim()) return;
formNotes.push({ date: new Date().toISOString().slice(0, 10), text: input.value.trim() });
input.value = '';
renderNotesList();
schedulePartnerAutosave();
}
// ---------- PHOTO / LOGO PREVIEW ----------
// Converts a Google Drive "view" share link into a directly-embeddable image URL.
// Any other URL (a normal image link) is used as-is.
function driveFileId(url) {
if (!url) return null;
let m = url.match(/\/d\/([a-zA-Z0-9_-]{10,})/);
if (m) return m[1];
m = url.match(/[?&]id=([a-zA-Z0-9_-]{10,})/);
return m ? m[1] : null;
}
function normalizeImageUrl(url) {
const id = driveFileId(url);
return id ? `https://drive.google.com/uc?export=view&id=${id}` : url;
}
// Table-row avatars fall back to a placeholder icon if the image URL fails
// (e.g. a Drive file that isn't actually shared publicly).
function handleAvatarError(img) {
const div = document.createElement('div');
div.className = 'w-8 h-8 rounded-lg bg-gray-100 flex items-center justify-center text-gray-300 text-xs flex-shrink-0';
div.innerHTML = '';
img.replaceWith(div);
}
function updatePfPhotoPreview() {
const url = document.getElementById('pf_photoUrl').value.trim();
const img = document.getElementById('pf_photoPreview');
const placeholder = document.getElementById('pf_photoPreviewPlaceholder');
if (url) {
img.src = normalizeImageUrl(url);
img.classList.remove('hidden');
placeholder.classList.add('hidden');
} else {
img.classList.add('hidden');
img.removeAttribute('src');
placeholder.classList.remove('hidden');
}
}
// If the image fails to load (e.g. a Drive file that isn't shared publicly), fall
// back to showing the placeholder rather than a broken-image icon.
document.addEventListener('error', e => {
if (e.target && e.target.id === 'pf_photoPreview') {
e.target.classList.add('hidden');
const ph = document.getElementById('pf_photoPreviewPlaceholder');
if (ph) ph.classList.remove('hidden');
}
}, true);
// ---------- CLICKABLE URL FIELDS (open without leaving the field editable) ----------
function openUrlField(fieldId) {
let val = (document.getElementById(fieldId).value || '').trim();
if (!val) { showToast('Nothing to open — this field is empty.'); return; }
if (fieldId === 'pf_whatsapp') {
const digits = val.replace(/[^\d]/g, '');
if (digits) { window.open('https://wa.me/' + digits, '_blank', 'noopener'); return; }
}
if (!/^https?:\/\//i.test(val)) val = 'https://' + val;
window.open(val, '_blank', 'noopener');
}
// ---------- DOCUMENTS (Google Drive links, opened in a floating in-app viewer) ----------
function driveEmbedUrl(url) {
const id = driveFileId(url);
return id ? `https://drive.google.com/file/d/${id}/preview` : url;
}
function driveDownloadUrl(url) {
const id = driveFileId(url);
return id ? `https://drive.google.com/uc?export=download&id=${id}` : url;
}
function renderDocumentsList() {
const el = document.getElementById('pf_documentsList');
if (!formDocuments.length) { el.innerHTML = '
`;
const selectedAttachments = collectSelectedAttachments();
try {
const response = await callEmailFunction({
from: `NEXTGENZ CENTRE <${CONFIG.SENDER_EMAIL}>`,
to: [toEmail],
cc: ccList.length ? ccList : undefined,
reply_to: CONFIG.REPLY_TO,
subject: subject,
html: beautifulHTML,
attachments: selectedAttachments.length ? selectedAttachments : undefined
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(data.message || ('Server responded with HTTP ' + response.status));
}
await markEmailSent(true);
document.getElementById('em_sentMsg').textContent = 'Email sent successfully via Netlify Function!';
document.getElementById('em_sentMsg').classList.remove('hidden');
} catch (err) {
console.error('Email send failed:', err);
errEl.textContent = 'Error sending email: ' + (err.message || 'Unknown error');
errEl.classList.remove('hidden');
} finally {
btn.disabled = false;
btn.innerHTML = ' Send Email Now via Resend';
}
}
async function markEmailSent(alreadySentByHub = false) {
const to = document.getElementById('em_to').value.trim();
const ccList = getChipValues('em_cc_chips');
const subject = document.getElementById('em_subject').value.trim();
const errEl = document.getElementById('em_errMsg');
const sentEl = document.getElementById('em_sentMsg');
errEl.classList.add('hidden'); sentEl.classList.add('hidden');
const btn = document.getElementById('em_markSentBtn');
btn.disabled = true; btn.innerHTML = ' Updating...';
const nowStr = new Date().toLocaleString('en-GB', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
const recipientsLine = 'To: ' + (to || '-') + (ccList.length ? '\nCC: ' + ccList.join(', ') : '');
try {
const partnerId = document.getElementById('emailModal').dataset.partnerId;
const agentId = document.getElementById('emailModal').dataset.agentId;
const followUpMode = document.getElementById('emailModal').dataset.followUp || '';
if (agentId) {
const a = allAgents.find(x => x.id === agentId);
const noteText = `Recruitment email sent: "${subject}"\n${recipientsLine}\nSent: ${nowStr}`;
const note = { date: new Date().toISOString().slice(0, 10), text: noteText };
const newNotes = (a && a.notes ? [...a.notes] : []).concat([note]);
const updates = { notes: newNotes, updatedAt: new Date().toISOString() };
if (a && (a.status === 'To Contact' || !a.status)) { updates.status = 'Contacted'; updates.dateSent = note.date; }
await db.collection('agents').doc(agentId).update(updates);
const notifyMsg = `Recruitment email sent to ${a ? (a.name || a.country) : to}\n\nSubject: ${subject}\n${recipientsLine}\n\nSent: ${nowStr}`;
notifyWhatsApp('NEXTGENZ Hub: Recruitment email sent to ' + (a ? (a.name || a.country) : to));
notifyEmail(`Recruitment email sent to ${a ? (a.name || a.country) : to}`, notifyMsg);
showToast('Email sent to ' + (a ? (a.name || a.country) : to));
loadAgents();
} else if (partnerId) {
const p = allPartners.find(x => x.id === partnerId);
const baseLabel = followUpMode === 'reply' ? 'Follow-up email sent (referencing their reply)'
: followUpMode === 'noreply' ? 'Follow-up email sent (no answer yet)'
: 'Email sent via NEXTGENZ Hub';
const noteText = `${baseLabel}: "${subject}"\n${recipientsLine}\nSent: ${nowStr}`;
const note = { date: new Date().toISOString().slice(0, 10), text: noteText };
const newNotes = (p && p.notes ? [...p.notes] : []).concat([note]);
const updates = { notes: newNotes, updatedAt: new Date().toISOString() };
if (followUpMode) { updates.status = 'Follow Up'; }
else if (p && (p.status === 'To Contact' || !p.status)) { updates.status = 'Email Sent'; updates.dateSent = note.date; }
await db.collection('partners').doc(partnerId).update(updates);
const notifyMsg = `Email sent to ${p ? p.name : to}\n\nSubject: ${subject}\n${recipientsLine}\n\nSent: ${nowStr}`;
notifyWhatsApp('NEXTGENZ Hub: Email sent to ' + (p ? p.name : to));
notifyEmail(`Email sent to ${p ? p.name : to}`, notifyMsg);
showToast('Email sent to ' + (p ? p.name : to));
loadPartners();
} else {
const notifyMsg = `Email sent to ${to || 'recipient'}\n\nSubject: ${subject}\n${recipientsLine}\n\nSent: ${nowStr}`;
notifyWhatsApp('NEXTGENZ Hub: Email sent to ' + (to || 'recipient'));
notifyEmail(`Email sent to ${to || 'recipient'}`, notifyMsg);
showToast('Email sent to ' + (to || 'recipient'));
}
sentEl.classList.remove('hidden');
setTimeout(() => hideEl('emailModal'), 1200);
} catch (err) {
console.error('Mark as sent failed:', err);
errEl.textContent = 'Could not save: ' + (err && err.message ? err.message : 'unknown error');
errEl.classList.remove('hidden');
}
btn.disabled = false; btn.innerHTML = ' Mark as Sent (notify me + update status)';
}
// ---------- EXPORT ----------
function exportCSV() {
if (activeTab === 'students') { exportStudentsCSV(); return; }
if (activeTab === 'agents') { exportAgentsCSV(); return; }
const cols = ['name','acronym','type','location','priority','email','status','dateSent','expiry','portalUser','portalPass','portalName','howToApply','visaContact','whatsapp','appFee','commission'];
const rows = [cols.join(',')];
allPartners.forEach(p => {
rows.push(cols.map(c => csvEscape(p[c])).join(','));
});
downloadCSV(rows.join('\n'), 'nextgenz_partners.csv');
}
function csvEscape(val) {
val = (val === undefined || val === null) ? '' : String(val);
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
return '"' + val.replace(/"/g, '""') + '"';
}
return val;
}
function downloadCSV(content, filename) {
const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename;
document.body.appendChild(a); a.click(); document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ---------- IMPORT STARTER DATA ----------
async function importStarterData() {
if (!confirm('Import your saved universities and English centres into the tracker?')) return;
const btn = document.getElementById('importBtn');
btn.disabled = true; btn.textContent = 'Importing...';
const U = (name, acronym, ptype, location, priority, email, status, dateSent, extra) => ({
type: 'University', name, acronym, location, priority: (ptype + ' - ' + priority), email, status,
dateSent: dateSent || '', expiry: '', portalUser: '', portalPass: '', portalName: '', howToApply: '',
visaContact: '', whatsapp: '', appFee: '', commission: '', notes: [], ...(extra || {})
});
const E = (name, category, location, policy, action, website, status) => ({
type: 'English Centre', name, acronym: '', location, priority: category, email: '', status,
dateSent: '', expiry: '', portalUser: '', portalPass: '', portalName: website, howToApply: action,
visaContact: '', whatsapp: '', appFee: '', commission: policy, notes: []
});
const data = [
U('Asia Pacific University', 'APU', 'Private', 'Kuala Lumpur', 'P1: Quick Win (Aggressive Marketing)', 'partners@apu.edu.my / info@apu.edu.my', 'Email Sent', '2026-01-01'),
U('Management and Science University', 'MSU', 'Private', 'Shah Alam, Selangor', 'P1: Quick Win (Popular in Indonesia)', 'enquiry@msu.edu.my', 'Email Sent', '2026-01-04'),
U('City University', '', 'Private', 'Petaling Jaya, Selangor', 'P1: Quick Win (Flexible Entry)', 'imo@city.edu.my / ccd@upsi.edu.my', 'Email Sent', '2026-01-04'),
U('Lincoln University College', '', 'Private', 'Kelana Jaya, Selangor', 'P1: Quick Win (Medical/Health Focus)', 'international@lincoln.edu.my', 'Approved', '2026-01-04', {
expiry: '2027-01-24', portalUser: 'URM06916', portalPass: '9876543210', portalName: 'Lincoln Portal',
howToApply: 'Apply using portal. Email TO bhibhas@lincoln.edu.my CC: ia@lincoln.edu.my', visaContact: 'bhibhas@lincoln.edu.my',
whatsapp: '60123088136', appFee: '2026 FEES MYR document on file', commission: '2026 FEES MYR document on file'
}),
U('Binary University', '', 'Private', 'Puchong, Selangor', 'P1: Quick Win (Management/Business)', 'info@binary.edu.my', 'Email Sent', '2026-01-04'),
U('Al-Madinah International University', 'MEDIU', 'Private', 'Kuala Lumpur', 'P2: Strategic (Islamic/Arabic Niche)', 'marketing@mediu.edu.my', 'Email Sent'),
U('Universiti Pendidikan Sultan Idris', 'UPSI', 'Public', 'Tanjung Malim, Perak', 'P2: Strategic (In Progress)', 'iso@upsi.edu.my', 'Email Sent'),
U('Tenaga Nasional University', 'UNITEN', 'Private', 'Kajang / Putrajaya', 'P2: Strategic (Semi-Gov/Stable)', 'int_admin@uniten.edu.my', 'Email Sent'),
U('International Islamic University Malaysia', 'IIUM', 'Semi-Gov', 'Gombak / KL', 'P2: Strategic (Top Islamic Brand)', 'io@iium.edu.my', 'Email Sent'),
U('UCSI University', 'UCSI', 'Private', 'Kuala Lumpur', 'P3: General Private (Top Ranked)', 'international@ucsiuniversity.edu.my', 'Email Sent'),
U('Multimedia University', 'MMU', 'Private', 'Cyberjaya / Melaka', 'P3: General Private (Tech Focus)', 'intrecruit@mmu.edu.my', 'Email Sent'),
U('Universiti Teknologi PETRONAS', 'UTP', 'Private', 'Seri Iskandar, Perak', 'P3: General Private (Oil & Gas)', 'io@utp.edu.my', 'Email Sent'),
U('Universiti Kuala Lumpur', 'UniKL', 'Private', 'Kuala Lumpur', 'P3: General Private (Technical)', 'international@unikl.edu.my', 'Email Sent'),
U('Infrastructure University Kuala Lumpur', 'IUKL', 'Private', 'Kajang, Selangor', 'P3: General Private (Engineering)', 'international@iukl.edu.my', 'To Contact'),
U('University of Cyberjaya', 'UoC', 'Private', 'Cyberjaya', 'P3: General Private (Medical)', 'international@cyberjaya.edu.my', 'To Contact'),
U('MAHSA University', '', 'Private', 'Bandar Saujana Putra, Selangor', 'P3: General Private (Health)', 'prospective@mahsa.edu.my', 'To Contact'),
U('SEGi University', '', 'Private', 'Kota Damansara, Selangor', 'P3: General Private (Mass Market)', 'infokd@segi.edu.my', 'To Contact'),
U('HELP University', '', 'Private', 'Kuala Lumpur / Shah Alam', 'P3: General Private (Psychology/Biz)', 'international@help.edu.my', 'To Contact'),
U('INTI International University', '', 'Private', 'Nilai / Subang', 'P3: General Private', 'international@newinti.edu.my', 'To Contact'),
U("Taylor's University", '', 'Private', 'Subang Jaya, Selangor', 'P3: General Private (Premium)', 'international@taylors.edu.my', 'To Contact'),
U('Sunway University', '', 'Private', 'Sunway City, Selangor', 'P3: General Private (Premium)', 'info@sunway.edu.my', 'To Contact'),
U('UOW Malaysia KDU', 'UOW', 'Private', 'Shah Alam, Selangor', 'P3: General Private', 'best@uow.edu.my', 'To Contact'),
U('UNITAR International University', '', 'Private', 'Kelana Jaya, Selangor', 'P3: General Private', 'enquiry@unitar.my', 'To Contact'),
U('UNIRAZAK', '', 'Private', 'Kuala Lumpur', 'P3: General Private', 'marketing@unirazak.edu.my', 'To Contact'),
U('Universiti Selangor', 'UNISEL', 'Private', 'Selangor', 'P3: General Private', 'marketing@unisel.edu.my', 'To Contact'),
U('Nilai University', '', 'Private', 'Nilai, Negeri Sembilan', 'P3: General Private', 'marketing@nilai.edu.my', 'To Contact'),
U('Putra Business School', 'PBS', 'Private', 'Serdang, Selangor', 'P3: General Private (MBA)', 'enquiry@putrabs.edu.my', 'To Contact'),
U('ALFA University College', '', 'Private', 'Subang Jaya, Selangor', 'P3: General Private (Design)', 'enquiry@alfa.edu.my', 'Approved', '2026-01-28', {
expiry: '2027-01-28', howToApply: 'Apply using Email: severo@alfa.edu.my', whatsapp: '60174008957'
}),
U('International Medical University', 'IMU', 'Private', 'Bukit Jalil, KL', 'P3: General Private (Premium Med)', 'international@imu.edu.my', 'To Contact'),
U('TAR UMT', '', 'Private', 'Kuala Lumpur', 'P3: General Private', 'iso@tarc.edu.my', 'To Contact'),
U('Asia School of Business', 'ASB', 'Private', 'Kuala Lumpur', 'P3: General Private', 'admissions@asb.edu.my', 'To Contact'),
U('Open University Malaysia', 'OUM', 'Private', 'Kelana Jaya / Nationwide', 'P3: General Private (Adult/Online)', 'international@oum.edu.my', 'To Contact'),
U('Asia e University', 'AeU', 'Private', 'Subang Jaya, Selangor', 'P3: General Private (Online)', 'enquiries@aeu.edu.my', 'To Contact'),
U('UNIMY', '', 'Private', 'Cyberjaya', 'P3: General Private (Digital)', 'enquiry@unimy.edu.my', 'To Contact'),
U('Berjaya University College', '', 'Private', 'Kuala Lumpur', 'P3: General Private (Hospitality)', 'berjaya@berjaya.edu.my', 'To Contact'),
U('Meritus University', '', 'Private', 'Kuala Lumpur', 'P3: General Private (Maritime)', 'enquiry@meritus.edu.my', 'To Contact'),
U('Monash University Malaysia', '', 'Foreign Branch', 'Sunway City, Selangor', 'P4: Foreign Branch (Prestige)', 'mum.enquiry@monash.edu', 'To Contact'),
U('University of Nottingham Malaysia', '', 'Foreign Branch', 'Semenyih, Selangor', 'P4: Foreign Branch (Prestige)', 'international.enquiries@nottingham.edu.my', 'To Contact'),
U('Heriot-Watt University Malaysia', '', 'Foreign Branch', 'Putrajaya', 'P4: Foreign Branch (Prestige)', 'hwum@hw.ac.uk', 'To Contact'),
U('Xiamen University Malaysia', '', 'Foreign Branch', 'Sepang, Selangor', 'P4: Foreign Branch (Prestige)', 'enquiry@xmu.edu.my', 'To Contact'),
U('Universiti Malaya', 'UM', 'Public', 'Kuala Lumpur', 'P5: Public (Hard/Strict)', 'isc@um.edu.my', 'To Contact'),
U('Universiti Kebangsaan Malaysia', 'UKM', 'Public', 'Bangi, Selangor', 'P5: Public (Strict)', 'ukmglobal@ukm.edu.my', 'To Contact'),
U('Universiti Putra Malaysia', 'UPM', 'Public', 'Serdang, Selangor', 'P5: Public (Strict)', 'intl@upm.edu.my', 'To Contact'),
U('Universiti Sains Malaysia', 'USM', 'Public', 'Penang', 'P5: Public (Strict)', 'international@usm.my', 'To Contact'),
U('Universiti Teknologi Malaysia', 'UTM', 'Public', 'Johor / KL', 'P5: Public (Strict)', 'international@utm.my', 'To Contact'),
U('Universiti Teknologi MARA', 'UiTM', 'Public', 'Shah Alam / Nationwide', 'P5: Public (Strict)', 'ois@uitm.edu.my', 'To Contact'),
U('Universiti Pertahanan Nasional Malaysia', 'UPNM', 'Public', 'Kuala Lumpur', 'P5: Public (Strict)', 'io@upnm.edu.my', 'To Contact'),
U('Universiti Utara Malaysia', 'UUM', 'Public', 'Sintok, Kedah', 'P5: Public (Strict)', 'ciac@uum.edu.my', 'To Contact'),
U('Universiti Tun Hussein Onn Malaysia', 'UTHM', 'Public', 'Batu Pahat, Johor', 'P5: Public (Strict)', 'io@uthm.edu.my', 'To Contact'),
U('Universiti Teknikal Malaysia Melaka', 'UTeM', 'Public', 'Melaka', 'P5: Public (Strict)', 'int_office@utem.edu.my', 'To Contact'),
U('Universiti Malaysia Pahang', 'UMP', 'Public', 'Pahang', 'P5: Public (Strict)', 'io@ump.edu.my', 'To Contact'),
U('University College of Islam Melaka', 'UNIMEL', 'Public', 'Melaka', 'P5: Public (Strict)', 'info@unimel.edu.my', 'To Contact'),
E('ACE Language Centre', 'Quick Start / Visa', 'Mont Kiara (Plaza Mont Kiara)', 'Very open - advertises attractive commissions', 'Apply Online: fill the "Become Agent" form', 'acelanguagecentre.my/become-agent', 'Email Sent'),
E('Britannia Language Centre', 'Quick Start', 'Bukit Bintang (Menara Genesis HSBC)', 'Fastest payout - claims commission paid within 3 days', 'Walk-in: visit in person, city center', 'britannia.edu.my/become-our-partner', 'Email Sent'),
E('VI Academy', 'Visa Focus', 'Sunway / KL', 'Visa specialist, good for 1-year visas', 'Online Form: submit "Agent Registration" form', 'viacademy.com/agent-registration', 'To Contact'),
E('Sheffield Academy', 'Vocational + English', 'Damansara Heights (Wisma UOA)', 'Job focused, good for hospitality-minded students', 'Email your profile to info@sheffield.edu.my', 'sheffield.edu.my/agents', 'To Contact'),
E('EMS Language Centre', 'International Hub', 'KLCC Area (Megan Avenue II)', 'Walk-in preferred for agents', 'Walk-in with company profile to the admin office', 'ems.edu.my/partners', 'To Contact'),
E('Awesome English Center', 'Camp / Holiday', 'KL City (Megan Avenue I)', 'High marketing reach, popular with Arab/Central Asian students', 'Walk-in (near EMS, same area)', 'awesome.edu.my', 'To Contact'),
E('LEA English Centre', 'Uni Pathway', 'Petaling Jaya (Ara Damansara)', 'University feeder, good for serious students', 'Call +603-7846 6693, ask for Marketing Manager', 'lea.edu.my', 'To Contact'),
E('Stratford Int. Centre', 'IELTS Specialist', 'KL City (G Tower, Jalan Tun Razak)', 'Exam focused, best for high IELTS score needs', 'Email info@stratford.edu.my', 'stratford.edu.my', 'To Contact'),
E('ELS Language Centres', 'Uni Pathway (Top)', 'Bukit Bintang (Times Square)', 'Strict - requires company docs (SSM) & thorough check', 'Online Portal: upload docs to their agent portal', 'els.edu.my/new-agent', 'To Contact'),
E('British Council', 'Premium Brand', 'Jalan Ampang', 'Very strict, hard to get, but sells itself', 'Formal email with a very professional proposal; long wait time', 'britishcouncil.my', 'To Contact'),
E('Erican Language Centre', 'Local Brand', 'KLCC (Megan Avenue II)', 'Established, good for long-term trust', 'Walk-in (while visiting EMS/Awesome)', 'erican.edu.my', 'To Contact')
];
try {
const batchSize = 20;
for (let i = 0; i < data.length; i += batchSize) {
const chunk = data.slice(i, i + batchSize);
await Promise.all(chunk.map(d => db.collection('partners').add(d)));
}
notifyWhatsApp('NEXTGENZ Tracker: starter list of ' + data.length + ' partners imported.');
showToast('Imported ' + data.length + ' partners.');
loadPartners();
} catch (err) {
console.error(err);
alert('Import failed partway through - check your connection and try again.');
}
btn.disabled = false; btn.innerHTML = ' Import Starter List';
}
// ================= AGENTS (overseas sub-agent recruitment, 30% commission) =================
// Target country list pulled from your "Our Representatives" page. Malaysia is excluded
// (it's home base, not a recruitment market). HIGH_PRIORITY_COUNTRIES mirrors your existing
// Call Time Planner focus (Indonesia + MENA + the other regions you already prioritize).
const HIGH_PRIORITY_COUNTRIES = new Set([
'Indonesia','Egypt','Jordan','Algeria','Morocco','Iraq','Yemen','Saudi Arabia','Syria',
'Lebanon','Libya','Tunisia','Palestine','Kuwait','Oman','Bahrain','Nigeria','Ghana',
'Cameroon','Sudan','Bangladesh','Pakistan','Vietnam','Uzbekistan','Kazakhstan','Kyrgyzstan'
]);
const TARGET_COUNTRIES = [
'Afghanistan','Albania','Algeria','Australia','Azerbaijan','Bahrain','Bangladesh','Bhutan',
'Britain','Brunei','Myanmar','Burundi','Cameroon','Canada','Chad','Chile','China','Comoros',
'Democratic Republic of the Congo','Denmark','Djibouti','Dominican Republic','Ecuador','Egypt',
'Equatorial Guinea','Eritrea','Eswatini','Ethiopia','Philippines','The Gambia','Germany','Ghana',
'Guyana','Guinea','Hong Kong','India','Indonesia','Iran','Iraq','Italy','Japan','Jordan',
'Kazakhstan','Kenya','Kuwait','Kyrgyzstan','Lebanon','Liberia','Libya','Malawi','Maldives',
'Mali','Mauritius','Morocco','Mozambique','Nepal','New Zealand','Nigeria','Oman','Pakistan',
'Palestine','Papua New Guinea','Romania','Russia','Rwanda','Saudi Arabia','Senegal','Seychelles',
'Sierra Leone','Singapore','Somalia','South Africa','South Sudan','Sri Lanka','Sudan','Syria',
'Taiwan','Tajikistan','Tanzania','Thailand','Timor-Leste','Trinidad and Tobago','Tunisia',
'Turkmenistan','Uganda','Uzbekistan','Vietnam','Yemen','Zambia','Zimbabwe'
];
let allAgents = [];
let currentEditAgentId = null;
let selectedAgentIds = new Set();
let formAgentNotes = [];
let afAutosaveTimer = null;
let afLastStatus = null;
let afSuppressEvents = false;
const AF_TEXT_FIELDS = ['af_country','af_name','af_contactPerson','af_whatsapp','af_commission','af_dateSent','af_source'];
const AGENT_NOTIFY_STATUSES = ['Contacted','Negotiating','Signed','Not Interested'];
async function loadAgents() {
try {
const snap = await db.collection('agents').get();
allAgents = snap.docs.map(d => ({ id: d.id, ...d.data() }));
renderAgentStats();
renderAgentAttention();
renderAgentTable();
} catch (err) {
console.error(err);
document.getElementById('agentsTableBody').innerHTML = '
Could not load data. Check your connection. ' + String((err && (err.code || err.message)) || err).replace(/[<>&]/g,'') + (location.protocol !== 'https:' ? ' — this file is opened from your phone, not from your website. Open it from nextgenzcentre.space.' : '') + '
`).join('');
}
function addAgentNoteToForm() {
const input = document.getElementById('af_newNote');
if (!input.value.trim()) return;
formAgentNotes.push({ date: new Date().toISOString().slice(0, 10), text: input.value.trim() });
input.value = '';
renderAgentNotesList();
scheduleAgentAutosave();
}
function collectAgentFormData() {
return {
status: document.getElementById('af_status').value,
country: document.getElementById('af_country').value.trim(),
name: document.getElementById('af_name').value.trim(),
contactPerson: document.getElementById('af_contactPerson').value.trim(),
priority: document.getElementById('af_priority').value,
email: getChipValues('af_email_chips').join(', '),
whatsapp: document.getElementById('af_whatsapp').value.trim(),
commission: document.getElementById('af_commission').value.trim() || '30%',
dateSent: document.getElementById('af_dateSent').value,
source: document.getElementById('af_source').value.trim(),
notes: formAgentNotes
};
}
function scheduleAgentAutosave() {
if (afSuppressEvents) return;
setSaveStatus('af_saveStatus', 'saving');
clearTimeout(afAutosaveTimer);
afAutosaveTimer = setTimeout(autosaveAgent, 600);
}
async function autosaveAgent() {
const data = collectAgentFormData();
if (!data.country) { setSaveStatus('af_saveStatus', 'saved'); return; }
data.updatedAt = new Date().toISOString();
try {
if (currentEditAgentId) {
await db.collection('agents').doc(currentEditAgentId).update(data);
} else {
const ref = await db.collection('agents').add(data);
currentEditAgentId = ref.id;
document.getElementById('agentModalTitle').textContent = 'Edit Agent';
document.getElementById('deleteAgentBtn').classList.remove('hidden');
}
setSaveStatus('af_saveStatus', 'saved');
if (data.status !== afLastStatus && AGENT_NOTIFY_STATUSES.includes(data.status)) {
notifyUpdate('agent', data.name || data.country, data.status);
}
afLastStatus = data.status;
allAgents = allAgents.filter(a => a.id !== currentEditAgentId).concat([{ id: currentEditAgentId, ...data }]);
renderAgentStats(); renderAgentAttention(); renderAgentTable();
} catch (err) {
console.error(err);
setSaveStatus('af_saveStatus', 'error');
}
}
async function deleteAgent() {
if (!currentEditAgentId || !confirm('Delete this agent row permanently?')) return;
await db.collection('agents').doc(currentEditAgentId).delete();
closeAgentModal();
showToast('Agent deleted.');
loadAgents();
}
// ---------- DRAFT AGENT RECRUITMENT EMAIL (reuses the shared Email Modal) ----------
function draftAgentEmailFor(id) {
let name, country, email;
if (id) {
const a = allAgents.find(x => x.id === id);
name = a.name || (a.country + ' Representative'); country = a.country; email = a.email;
} else {
name = document.getElementById('af_name').value.trim() || (document.getElementById('af_country').value.trim() + ' Representative');
country = document.getElementById('af_country').value.trim() || 'your country';
email = getChipValues('af_email_chips').join(', ');
}
const emailList = splitEmails(email);
document.getElementById('em_to').value = emailList[0] || '';
setChipValues('em_cc_chips', [CONFIG.ADMIN_EMAIL, ...emailList.slice(1)].filter(Boolean));
document.getElementById('em_subject').value = AGENT_TEMPLATE.subject.replace(/\{\{name\}\}/g, name).replace(/\{\{country\}\}/g, country);
document.getElementById('em_body').value = AGENT_TEMPLATE.body.replace(/\{\{name\}\}/g, name).replace(/\{\{country\}\}/g, country);
document.getElementById('emailModal').dataset.partnerId = '';
document.getElementById('emailModal').dataset.agentId = id || '';
document.getElementById('emailModal').dataset.followUp = '';
document.getElementById('em_copiedMsg').classList.add('hidden');
document.getElementById('em_sentMsg').classList.add('hidden');
document.getElementById('em_errMsg').classList.add('hidden');
refreshEmailAttachOptions();
showEl('emailModal');
}
// ---------- WHATSAPP DRAFTS (opens WhatsApp with the message pre-filled, ready to send) ----------
function openWhatsAppDraft(rawNumber, message) {
const digits = (rawNumber || '').replace(/[^\d]/g, '');
if (!digits) {
// No number on file — copy the message so it's not lost, and let them paste it manually.
navigator.clipboard.writeText(message).then(() => {
showToast('No WhatsApp number on file — message copied instead.');
});
return;
}
window.open(`https://wa.me/${digits}?text=${encodeURIComponent(message)}`, '_blank', 'noopener');
}
const AGENT_WHATSAPP_TEMPLATE = `Hi {{name}}! 👋 This is NEXTGENZ CENTRE, an education consultancy in Kuala Lumpur, Malaysia.
We're expanding our recruitment network in {{country}} and would love to bring you on as an official partner — 30% commission per enrolled student, full support on applications & visas, and marketing materials provided.
Would you be open to a quick chat about partnering with us? 🇲🇾`;
function draftAgentWhatsAppFor(id) {
let name, country, whatsapp;
if (id) {
const a = allAgents.find(x => x.id === id);
name = a.name || (a.country + ' Team'); country = a.country; whatsapp = a.whatsapp;
} else {
name = document.getElementById('af_name').value.trim() || (document.getElementById('af_country').value.trim() + ' Team');
country = document.getElementById('af_country').value.trim() || 'your country';
whatsapp = document.getElementById('af_whatsapp').value.trim();
}
const message = AGENT_WHATSAPP_TEMPLATE.replace(/\{\{name\}\}/g, name).replace(/\{\{country\}\}/g, country);
openWhatsAppDraft(whatsapp, message);
}
const PARTNER_WHATSAPP_TEMPLATE = `Hi {{name}} Team! 👋 This is NEXTGENZ CENTRE, an education consultancy in Kuala Lumpur.
We'd love to explore a Student Recruitment Agent partnership with you — we currently have students from Indonesia and the MENA region ready to apply.
Could we get in touch with the right person on your side to move this forward? 🎓`;
function draftPartnerWhatsAppFor(id) {
let name, whatsapp;
if (id) {
const p = allPartners.find(x => x.id === id);
name = p.name; whatsapp = p.whatsapp;
} else {
name = document.getElementById('pf_name').value.trim() || 'Team';
whatsapp = document.getElementById('pf_whatsapp').value.trim();
}
const message = PARTNER_WHATSAPP_TEMPLATE.replace(/\{\{name\}\}/g, name);
openWhatsAppDraft(whatsapp, message);
}
// ---------- SEED TARGET COUNTRIES ----------
async function importAgentCountries() {
const existing = new Set(allAgents.map(a => (a.country || '').toLowerCase()));
const missing = TARGET_COUNTRIES.filter(c => !existing.has(c.toLowerCase()));
if (missing.length === 0) { showToast('Every target country is already on your list.'); return; }
if (!confirm(`Add ${missing.length} country row(s) you don't have yet, ready for sub-agent recruitment (30% commission, "To Contact")?`)) return;
const btn = document.getElementById('importAgentsBtn');
btn.disabled = true; btn.innerHTML = ' Adding...';
const data = missing.map(country => ({
status: 'To Contact', country, name: '', contactPerson: '',
priority: HIGH_PRIORITY_COUNTRIES.has(country) ? 'High' : 'Standard',
email: '', whatsapp: '', commission: '30%', dateSent: '', source: '', notes: [],
updatedAt: new Date().toISOString()
}));
try {
const batchSize = 20;
for (let i = 0; i < data.length; i += batchSize) {
const chunk = data.slice(i, i + batchSize);
await Promise.all(chunk.map(d => db.collection('agents').add(d)));
}
notifyWhatsApp('NEXTGENZ Hub: ' + data.length + ' target countries added to Agents tracker.');
showToast('Added ' + data.length + ' target countries.');
loadAgents();
} catch (err) {
console.error(err);
alert('Import failed partway through - check your connection and try again.');
}
btn.disabled = false; btn.innerHTML = ' Add Target Countries';
}
// ---------- EXPORT (agents) ----------
function exportAgentsCSV() {
const cols = ['country','priority','name','contactPerson','email','whatsapp','commission','status','dateSent','source'];
const rows = [cols.join(',')].concat(allAgents.map(a => cols.map(c => csvEscape(a[c])).join(',')));
downloadCSV(rows.join('\n'), 'nextgenz-agents.csv');
}
// ================= STUDENTS =================
const DOC_FIELDS = [
{ key: 'passportFront', label: 'Passport Front' },
{ key: 'passportPhoto', label: 'Passport Photo' },
{ key: 'origTranscript', label: 'Original Transcript' },
{ key: 'origCertificate', label: 'Original Certificate' },
{ key: 'transTranscript', label: 'Translated Transcript' },
{ key: 'transCertificate', label: 'Translated Certificate' },
{ key: 'cv', label: 'CV / Resume' },
{ key: 'englishQual', label: 'English Qualification' },
{ key: 'referee', label: 'Referee / Support' },
{ key: 'researchProposal', label: 'Research Proposal' },
{ key: 'notarial', label: 'Notarial' },
{ key: 'onlineVerification', label: 'Online Verification' }
];
const STUDENT_STAGES = ['Documents Collection','Application Submitted','Offer Received','Visa Processing','Ticket & Arrival','Registered','Completed'];
let allStudents = [];
let currentEditStudentId = null;
let selectedStudentIds = new Set();
let formDocs = {};
async function loadStudents() {
try {
const snap = await db.collection('students').get();
allStudents = snap.docs.map(d => ({ id: d.id, ...d.data() }));
populateCountryFilter();
renderStudentStats();
renderStudentAttention();
renderStudentTable();
} catch (err) {
console.error(err);
document.getElementById('studentsTableBody').innerHTML = '
Could not load data. Check your connection. ' + String((err && (err.code || err.message)) || err).replace(/[<>&]/g,'') + (location.protocol !== 'https:' ? ' — this file is opened from your phone, not from your website. Open it from nextgenzcentre.space.' : '') + '
`).join('');
}
// Instant email brief using the existing Resend notification pipeline — no new deploy needed.
async function sendMarketingBriefNow() {
const btn = document.getElementById('mktBriefBtn');
btn.disabled = true;
btn.innerHTML = ' Sending...';
const keys = ['fb', 'ig', 'snap', 'wechat'];
const lines = keys.map(k => `${PLATFORM_TASKS[k].label}: ${todayTaskFor(k)}${mktDailyDoc[k] ? ' (already done today)' : ''}`).join('\n\n');
await notifyEmail("Today's Marketing Brief", lines);
btn.disabled = false;
btn.innerHTML = ' Email Me Today\'s Brief';
showToast('Brief emailed to ' + CONFIG.ADMIN_EMAIL);
}
// Browser-native reminder: fires while the tab is open if tasks are still pending
// past mid-morning MYT. Checked once on load and then hourly.
async function enableMktNotifications() {
const statusEl = document.getElementById('mktNotifyStatus');
if (!('Notification' in window)) { statusEl.textContent = 'Not supported in this browser.'; return; }
const perm = await Notification.requestPermission();
if (perm === 'granted') {
statusEl.textContent = 'Reminders on while this tab is open.';
checkMktNotifyDue();
setInterval(checkMktNotifyDue, 60 * 60 * 1000);
} else {
statusEl.textContent = 'Permission not granted.';
}
}
let __mktLastNotifyKey = null;
function checkMktNotifyDue() {
if (Notification.permission !== 'granted') return;
const myt = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Kuala_Lumpur' }));
if (myt.getHours() < 10) return; // don't nag before 10am MYT
const key = todayKeyMYT();
const pending = ['fb', 'ig', 'snap', 'wechat'].filter(k => !mktDailyDoc[k]);
if (pending.length === 0 || __mktLastNotifyKey === key) return;
__mktLastNotifyKey = key;
new Notification('NEXTGENZ Hub — Marketing pending', {
body: pending.map(k => PLATFORM_TASKS[k].label).join(', ') + ' still need today\'s post.'
});
}
const CALL_TIMES = [
{ time: '09:00', region: 'Indochina & West Indonesia', countries: 'Vietnam · Thailand · Indonesia (Jakarta)', strategy: 'Start here — these students are just waking up (8 AM local). Best for Southeast Asian recruitment.' },
{ time: '10:00', region: 'South Asia (East)', countries: 'Bangladesh · Kyrgyzstan', strategy: 'Shift focus to Central Asian borders and Bangladesh.' },
{ time: '11:00', region: 'Central Asia & Pakistan', countries: 'Pakistan · Uzbekistan · Kazakhstan', strategy: 'High potential — huge market for English courses, 8 AM local.' },
{ time: '12:00', region: 'The Gulf (Early)', countries: 'UAE (Dubai) · Oman', strategy: 'First Arab markets waking up now.' },
{ time: '13:00', region: 'The Gulf (Core) & Russia', countries: 'Saudi Arabia · Qatar · Kuwait · Russia (Moscow)', strategy: 'Heavy hitters — 8 AM in Riyadh and Mecca. Focus heavily here before lunch.' },
{ time: '14:00', region: 'Levant & East Africa', countries: 'Egypt · Jordan · Turkey · Sudan', strategy: 'Arabic speakers — Egypt and Jordan are key markets for Malaysia, 8 AM local.' },
{ time: '15:00', region: 'North Africa (Maghreb)', countries: 'Algeria · Tunisia · Morocco', strategy: 'Main goal window — exactly 8 AM in Algiers. Dedicate this hour to Algerian leads.' },
{ time: '16:00', region: 'West Africa', countries: 'Nigeria · Cameroon', strategy: 'Huge demand for Nigerian students coming to Malaysia, 8 AM local.' },
{ time: '17:00', region: 'West Africa (Atlantic)', countries: 'Ghana · Senegal', strategy: 'Final shift — wrap up with West African leads waking up at GMT+0.' }
];
// ---------- BRAND KIT & CAROUSEL ENGINE ----------
// Every generated post follows one fixed "Master Design System" so the AI image
// generator renders consistent branding across all 40+ carousels: same character
// (slide 1 only), same crest logo, same navy/gold palette, same typography and
// "Swipe Right" icon rules. Only the scene/icon and on-image text change per slide.
const BRAND_KIT = {
navy: '#0F1F38',
gold: '#C5A059',
ambassador: 'the specific young Asian female education advisor from the brand reference photo — long dark hair, warm welcoming smile, wearing a sharp tailored navy blue blazer with gold buttons, standing confidently and looking directly at the camera, with an elegant Malaysian flag pin on her lapel',
crest: 'the NextGenz Centre crest shield logo (white and gold, with a graduation cap icon), with the full "NextGenz Centre" name in gold sans-serif directly beneath it'
};
const CAROUSEL_AESTHETICS = [
{ label: 'Cinematic', desc: 'hyper-realistic, cinematic corporate photography style, professional studio lighting, shallow depth of field' },
{ label: 'Line-Art', desc: 'high-end minimalist line-art illustration, clean flat design, limited color palette, simple geometric shapes' }
];
let carouselAestheticIdx = 0;
function toggleAesthetic() {
carouselAestheticIdx = carouselAestheticIdx === 0 ? 1 : 0;
renderContentGenerator();
renderAgentContentGenerator();
}
// Splits a caption into intro / CTA / hashtags — every caption template in this file
// is written as 3 blank-line-separated paragraphs, so this works generically for
// both the student captions and the agent recruitment captions.
function splitCaptionParts(caption) {
const paras = caption.split(/\n\s*\n/).map(s => s.trim()).filter(Boolean);
return {
intro: paras[0] || '',
cta: paras.length >= 3 ? paras[paras.length - 2] : (paras[1] || ''),
hashtags: paras.length ? paras[paras.length - 1] : ''
};
}
// slideKind: 'hook' (slide 1, features the character), 'value' (slide 2, vector icon
// only), or 'cta' (slide 3, vector icon + large centered logo, no character, no
// Swipe Right icon since it's the last slide in the sequence).
function buildSlidePrompt(sceneDetail, onImageText, slideLabel, slideKind) {
const aesthetic = CAROUSEL_AESTHETICS[carouselAestheticIdx].desc;
const isHook = slideKind === 'hook';
const isCta = slideKind === 'cta';
const characterLine = isHook
? `[REQUIRED INPUT: upload the brand reference photo of the advisor before generating this slide, for face/likeness consistency.] Main character: ${BRAND_KIT.ambassador}. She is the consistent brand ambassador and must look identical across every post. She appears on this first slide only — no character on the Value or CTA slides.`
: `No character on this slide — this is an informational/conversion slide, so use a clean premium vector-line icon graphic only, no photo-realistic person.`;
const flagLine = 'In the top-right corner, a small, subtle graphic of the Malaysian flag signals the study destination, consistent across every slide.';
const swipeLine = isCta
? 'This is the FINAL slide in the sequence — do NOT include a "Swipe Right" icon.'
: `Bottom-right corner: a small, semi-transparent white rounded rectangle with sans-serif "Swipe Right" text and a gold (${BRAND_KIT.gold}) pointer-finger icon pointing right.`;
return `${slideLabel} — carousel slide, 4:5 portrait aspect ratio, ${aesthetic}.
Background: a solid, clean Deep Navy Blue (${BRAND_KIT.navy}) environment — premium and evenly lit, like a minimalist high-end studio portrait setup or corporate lobby.
${characterLine}
Scene / central visual: ${sceneDetail}
Logo: displayed in the top center, ${BRAND_KIT.crest}. ${flagLine}
Typography: main title/hook in large, prominent, bold white sans-serif (Montserrat/Poppins style). Secondary explanatory text in medium-weight white sans-serif. Use gold (${BRAND_KIT.gold}) sans-serif for calls-to-action, numeric callouts, and icon outlines. Render this text directly in the image, large and fully legible: "${onImageText}"
${swipeLine}
No watermarks, no placeholder/lorem-ipsum text.`;
}
// Builds the standard 3-slide carousel following the fixed Master Design System:
// Slide 1 = Hook/Claim with character introduction (wide, attention-grabbing).
// Slide 2 = Explanation/Value — gold-line vector icon derived from the scene, no character.
// Slide 3 = Conversion/CTA — vector icon + large centered logo, no character, no Swipe Right icon.
function buildCarouselSlides(scene, headline, valueText, ctaText) {
return [
{ role: 'SLIDE 1 · THE HOOK', text: headline, prompt: buildSlidePrompt(`${scene}, dramatic wide establishing shot, scroll-stopping composition`, headline, 'SLIDE 1 · THE HOOK (Character Introduction)', 'hook') },
{ role: 'SLIDE 2 · THE VALUE', text: valueText, prompt: buildSlidePrompt(`a central, stylized gold-line vector icon that visually represents the idea of "${scene}" — minimalist, premium flat icon design (not a photo)`, valueText, 'SLIDE 2 · THE VALUE (Explanation)', 'value') },
{ role: 'SLIDE 3 · THE CTA', text: ctaText, prompt: buildSlidePrompt('a minimalist gold-line vector icon (e.g. a checklist being stamped with a checkmark) alongside the full NextGenz Centre crest logo and name, large and centered', ctaText, 'SLIDE 3 · THE CTA (Conversion)', 'cta') }
];
}
function renderSlideBlock(prefix, idx, slide) {
const textId = `${prefix}Slide${idx + 1}Text`;
const promptId = `${prefix}Slide${idx + 1}Prompt`;
return `
${slide.role}
On-image text
Image prompt
`;
}
const CONTENT_CALENDAR = [
{
day: 'Sun', template: 'Lifestyle', header: 'STUDY IN KUALA LUMPUR', hook: "Elite education. Affordable living. One of Asia's safest cities.", visual: 'KL skyline with Petronas Towers',
captionTemplateUniversity: `Want an incredible multicultural student experience in one of the safest cities in Asia? {{DESC}} puts you right in the middle of Kuala Lumpur — elite facilities, affordable luxury student housing, and a direct pipeline into dynamic corporate sectors.
DM "LIFESTYLE" or tap the WhatsApp link in bio to see what studying at {{DESC}} looks like day to day.
#StudyInMalaysia #StudyAbroad #InternationalStudents #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Want an incredible multicultural experience while you study English in one of the safest cities in Asia? {{DESC}} puts you right in the middle of Kuala Lumpur — modern classrooms, affordable student living, and a real path into further study or work.
DM "LIFESTYLE" or tap the WhatsApp link in bio to see what learning at {{DESC}} looks like day to day.
#StudyInMalaysia #StudyAbroad #InternationalStudents #NEXTGENZCENTRE`,
imageSceneUniversity: 'a stunning Kuala Lumpur skyline at golden hour featuring the Petronas Towers, aspirational and bright',
imageSceneEnglishCentre: 'a lively KL street scene with cafes and language school signage, warm evening light, welcoming urban lifestyle feel'
},
{
day: 'Mon', template: 'IELTS & Pathways', header: "DON'T PANIC ABOUT YOUR IELTS SCORE", hook: 'There is always another pathway.', visual: 'Reassuring advisor, calming gesture',
captionTemplateUniversity: `Worried that your IELTS score will ruin your plans for {{DESC}}? Don't panic — we help you find alternative pathways, conditional offers, and targeted foundation programs that give you the language support you need.
DM "ENGLISH" or tap the WhatsApp link in bio to learn your options today.
#StudyInMalaysia #IELTS #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Worried that your IELTS score will ruin your university dreams? Don't panic — {{DESC}} runs targeted preparation and foundation English programs built exactly to close that gap.
DM "ENGLISH" or tap the WhatsApp link in bio to learn your options today.
#StudyInMalaysia #IELTS #NEXTGENZCENTRE`,
imageSceneUniversity: 'a warm, reassuring advisor making a calming supportive gesture, soft studio lighting, comforting mood',
imageSceneEnglishCentre: 'a warm, reassuring advisor making a calming supportive gesture, soft studio lighting, comforting mood'
},
{
day: 'Tue', template: 'Price', header: 'SAVE MONEY. UPGRADE YOUR DEGREE.', hook: "Studying abroad doesn't have to mean decades of debt.", visual: 'Budget planning on a tablet',
captionTemplateUniversity: `Studying abroad doesn't have to mean decades of debt. {{DESC}} offers internationally recognized degrees at significantly lower tuition and living costs than the UK, US, or Australia.
DM "VALUE" or tap the WhatsApp link in bio and we'll map your budget for free.
#StudyInMalaysia #AffordableEducation #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Learning English abroad doesn't have to mean decades of debt. {{DESC}} offers internationally recognized courses at significantly lower tuition and living costs than the UK, US, or Australia.
DM "VALUE" or tap the WhatsApp link in bio and we'll map your budget for free.
#StudyInMalaysia #AffordableEnglish #NEXTGENZCENTRE`,
imageSceneUniversity: 'a tablet displaying a budget breakdown with a map pin, coffee cup, and apartment icon, reassuring high-trust visual',
imageSceneEnglishCentre: 'a tablet displaying a budget breakdown with a map pin, coffee cup, and apartment icon, reassuring high-trust visual'
},
{
day: 'Wed', template: 'Global Degree', header: 'GRADUATE WITH A UK, US, OR AUSTRALIAN DEGREE', hook: 'Study in Malaysia, graduate with a Western qualification.', visual: 'UK, USA & Australia flags',
captionTemplateUniversity: `Want a degree from a top UK, US, or Australian university without paying Western tuition fees? Through {{DESC}}'s exclusive Twinning and Dual Award programs, you study in Kuala Lumpur but graduate with an internationally recognized Western degree.
DM "GLOBAL" or tap the WhatsApp link in bio to see your options.
#StudyInMalaysia #TwinningProgram #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Planning to eventually study in the UK, US, or Australia? {{DESC}} builds the English foundation you need for exactly that — the first step before a Twinning or Dual Award pathway into a Western degree.
DM "GLOBAL" or tap the WhatsApp link in bio to see your options.
#StudyInMalaysia #TwinningProgram #NEXTGENZCENTRE`,
imageSceneUniversity: 'three elegant glowing 3D flags of the UK, USA, and Australia, cinematic aspirational composition',
imageSceneEnglishCentre: 'three elegant glowing 3D flags of the UK, USA, and Australia, cinematic aspirational composition'
},
{
day: 'Thu', template: 'Tech Majors', header: 'STUDY AI, SOFTWARE & CYBERSECURITY', hook: "Malaysia is Asia's rapidly growing tech hub.", visual: 'Holographic microchip & neural network',
captionTemplateUniversity: `AI. Software Engineering. Cybersecurity. The future is digital, and Malaysia is Asia's rapidly growing tech hub. {{DESC}} gives you direct industry connections in Tech and Computing degrees, future-proofing your career from day one.
DM "TECH" or tap the WhatsApp link in bio to find your program.
#StudyInMalaysia #TechDegree #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Dreaming of studying AI, Software Engineering, or Cybersecurity in Malaysia's booming tech hub? {{DESC}} gets your English to the level top Computing programs require, before you even apply.
DM "TECH" or tap the WhatsApp link in bio to find your path.
#StudyInMalaysia #TechDegree #NEXTGENZCENTRE`,
imageSceneUniversity: 'a glowing holographic microchip and neural network, high-tech corporate aesthetic, cinematic lighting',
imageSceneEnglishCentre: 'a glowing holographic microchip and neural network, high-tech corporate aesthetic, cinematic lighting'
},
{
day: 'Fri', template: 'Medical', header: 'DREAMING OF A CAREER IN MEDICINE?', hook: 'Navigate strict healthcare admission requirements with experts.', visual: 'Glowing DNA helix & stethoscope',
captionTemplateUniversity: `Medicine, Dentistry, or Pharmacy? The road to a healthcare degree in Malaysia is highly strict, and requirements for international students are complicated. {{DESC}} gives you a 100% free evaluation to check your academic eligibility for top medical programs.
DM "MEDICINE" or tap the WhatsApp link in bio to get your roadmap today.
#StudyInMalaysia #MedicalDegree #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Dreaming of Medicine, Dentistry, or Pharmacy but not sure your English is strong enough yet? {{DESC}} builds the academic English foundation top healthcare programs expect from international applicants.
DM "MEDICINE" or tap the WhatsApp link in bio to get your roadmap today.
#StudyInMalaysia #MedicalDegree #NEXTGENZCENTRE`,
imageSceneUniversity: 'a glowing holographic DNA helix beside a stethoscope, professional high-trust lighting',
imageSceneEnglishCentre: 'a glowing holographic DNA helix beside a stethoscope, professional high-trust lighting'
},
{
day: 'Sat', template: 'Career', header: "DON'T JUST GRADUATE. GET HIRED.", hook: 'The highest graduate employability rates in Malaysia.', visual: 'Upward career trend chart',
captionTemplateUniversity: `Don't just graduate — get hired. {{DESC}} boasts direct industry pipelines and some of the highest graduate employability rates in Malaysia, with mandatory internships and dedicated career placement support.
DM "CAREER" or tap the WhatsApp link in bio to start your planning.
#StudyInMalaysia #GraduateEmployability #NEXTGENZCENTRE`,
captionTemplateEnglishCentre: `Don't just learn English — build a career with it. {{DESC}} focuses on the professional fluency, presentation skills, and workplace terminology that make graduates genuinely hireable.
DM "CAREER" or tap the WhatsApp link in bio to start your planning.
#StudyInMalaysia #GraduateEmployability #NEXTGENZCENTRE`,
imageSceneUniversity: 'a confident graduate pointing toward a glowing upward trend chart, innovative aspirational mood',
imageSceneEnglishCentre: 'a confident graduate pointing toward a glowing upward trend chart, innovative aspirational mood'
}
];
// ---------- AGENT RECRUITMENT CONTENT (different audience: consultants/agencies, not students) ----------
const AGENT_RECRUIT_CONTENT = [
{
day: 'Sun', headline: 'GROW YOUR AGENCY WITH US',
caption: `Looking to grow your recruitment business? Partner with NEXTGENZ CENTRE and get direct access to Malaysian universities & English centres for your students, plus your own login on our Agent Portal to track every application.
DM "PARTNER" or message us on WhatsApp to register on the Agent Portal.
#StudyInMalaysia #AgentNetwork #NEXTGENZCENTRE`,
imageScene: 'a world map with glowing location pins across multiple countries, dark navy background, professional and global tone'
},
{
day: 'Mon', headline: 'TURN YOUR NETWORK INTO GUARANTEED COMMISSIONS',
caption: `Turn your student network into guaranteed, high-paying commissions. We pay out on every student you successfully refer — no cap, no complicated tiers, zero processing fees on your end.
Ready to start earning? Message us on WhatsApp to register on the Agent Portal today.
#StudyInMalaysia #AgentCommission #NEXTGENZCENTRE`,
imageScene: 'a rising growth chart on a laptop screen in a modern bright office, professional and optimistic tone'
},
{
day: 'Tue', headline: 'YOU RECRUIT. WE HANDLE THE REST.',
caption: `Focus on what you do best — finding students. We take care of university applications, offer letters, and visa guidance from our end, with zero processing fees passed on to you.
Want to partner with us? DM "PARTNER" or reach out on WhatsApp.
#StudyInMalaysia #EducationAgent #NEXTGENZCENTRE`,
imageScene: 'a confident consultant shaking hands with a client across a desk in a modern office, warm professional lighting'
},
{
day: 'Wed', headline: 'ARE YOU LOSING COMMISSIONS TO PAPERWORK?',
caption: `Are you losing out on university commissions because of complicated paperwork? Our Agent Portal gives you a frictionless way to track every student, every application, every payout — zero processing fees, zero sign-up cost.
DM "PARTNER" or message us on WhatsApp to get your Agent Portal login.
#StudyInMalaysia #FrictionlessPortal #NEXTGENZCENTRE`,
imageScene: 'an open laptop displaying a clean dashboard interface on a minimalist desk with a coffee cup, bright natural light'
},
{
day: 'Thu', headline: 'TRUSTED MALAYSIA-BASED PARTNER',
caption: `NEXTGENZ CENTRE is a registered Malaysia-based education consultancy — a stable, credible partner for your students' applications, backed by a real-time Agent Portal so you're never left guessing on status.
Interested in partnering? Message us on WhatsApp to learn more.
#StudyInMalaysia #TrustedPartner #NEXTGENZCENTRE`,
imageScene: 'a modern Kuala Lumpur skyline at dusk with city lights, professional and credible tone'
},
{
day: 'Fri', headline: 'JOIN OUR GLOBAL AGENT NETWORK',
caption: `You won't be recruiting alone — join a growing global network of agents already placing students in Malaysia with us, all tracking their commissions through one Agent Portal.
DM "PARTNER" or message us on WhatsApp to join the network.
#StudyInMalaysia #GlobalNetwork #NEXTGENZCENTRE`,
imageScene: 'a diverse group of professionals on a video call grid, collaborative and international feel, bright modern tone'
},
{
day: 'Sat', headline: 'MESSAGE US TO GET STARTED TODAY',
caption: `Ready to start referring students and earning commission? It only takes one message to get your Agent Portal login set up — zero fees, high payouts, full tracking.
Tap the WhatsApp link in bio, or DM "PARTNER" right now.
#StudyInMalaysia #StartToday #NEXTGENZCENTRE`,
imageScene: 'a smartphone on a clean desk displaying a chat conversation bubble icon, bright inviting light'
},
{
day: 'Extra 1', headline: 'FAST, UNCONDITIONAL UNIVERSITY OFFERS',
caption: `Losing student clients to visa and offer delays? Switch to the NEXTGENZ CENTRE Agent Portal. Our real-time tracking gives you transparent admission timelines and direct university communication, so you can hand your student clients fast, unconditional offers.
DM "FAST" or message us on WhatsApp to register as an agent.
#StudyInMalaysia #AgentNetwork #NEXTGENZCENTRE`,
imageScene: 'a sleek tablet displaying a large checkmark and "APPROVED" in gold text, confident professional lighting'
},
{
day: 'Extra 2', headline: 'NEVER LOSE A COMMISSION TO SLOW PAPERWORK',
caption: `Every delayed application is a commission at risk. Our Agent Portal keeps every student file moving — real-time status, direct university communication, and a support team on hand whenever you need to push a case forward.
DM "AGENTSUPPORT" or message us on WhatsApp to get set up.
#StudyInMalaysia #AgentNetwork #NEXTGENZCENTRE`,
imageScene: 'a consultant confidently reviewing a tablet dashboard with a reassuring smile, modern bright office'
}
];
let agentContentRegenCounter = 0;
function renderAgentContentGenerator() {
const idx = (dayOfYearMYT() + agentContentRegenCounter) % AGENT_RECRUIT_CONTENT.length;
const c = AGENT_RECRUIT_CONTENT[idx];
document.getElementById('agentCaption').value = c.caption;
const parts = splitCaptionParts(c.caption);
const slides = buildCarouselSlides(c.imageScene, c.headline, parts.intro, parts.cta);
document.getElementById('agentSlidesGrid').innerHTML = slides.map((s, i) => renderSlideBlock('agent', i, s)).join('');
document.getElementById('agentAudio').value = 'Confident, driven corporate/motivational instrumental — pick a royalty-free "success" or "momentum" style track from the platform\'s own audio library rather than a copyrighted trending song, so it never gets muted.';
const optionNum = (agentContentRegenCounter % AGENT_RECRUIT_CONTENT.length) + 1;
const noteEl = document.getElementById('agentOptionNote');
if (noteEl) noteEl.textContent = `Option ${optionNum} of ${AGENT_RECRUIT_CONTENT.length} available today.`;
const label = document.getElementById('agentAestheticLabel');
if (label) label.textContent = CAROUSEL_AESTHETICS[carouselAestheticIdx].label;
}
function regenerateAgentContent() {
agentContentRegenCounter++;
renderAgentContentGenerator();
}
// ---------- TODAY'S CONTENT GENERATOR (never uses partner names) ----------
let mktFeatureType = 'University';
let mktRegenCounter = 0;
function dayOfYearMYT() {
const myt = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Kuala_Lumpur' }));
const start = new Date(myt.getFullYear(), 0, 0);
return Math.floor((myt - start) / 86400000);
}
function sectorLabel(p) {
if (!p) return '';
if (p.type === 'English Centre') return 'English centre';
const seg = (p.priority || '').split(' - ')[0].trim().toLowerCase();
return (seg === 'private' || seg === 'public') ? seg + ' university' : 'university';
}
// Picks a partner matching today's featured category, cycling deterministically by
// day (so it's stable all day) plus a regenerate counter — but the output NEVER
// includes the partner's name, only its type/sector and city.
function pickFeaturedPartner() {
const approved = allPartners.filter(p => p.type === mktFeatureType && (p.status === 'Approved' || p.status === 'Email Sent' || p.status === 'Follow Up'));
const pool = approved.length ? approved : allPartners.filter(p => p.type === mktFeatureType);
if (!pool.length) return null;
const idx = (dayOfYearMYT() + mktRegenCounter) % pool.length;
return pool[idx];
}
function anonDescriptor(p) {
if (!p) return mktFeatureType === 'English Centre' ? 'one of our partner English centres' : 'one of our partner universities';
return `a top partner ${sectorLabel(p)}${p.location ? ' in ' + p.location : ''}`;
}
function renderContentGenerator() {
const angleIdx = (dayOfYearMYT() + mktRegenCounter) % CONTENT_CALENDAR.length;
const angle = CONTENT_CALENDAR[angleIdx];
const partner = pickFeaturedPartner();
const desc = anonDescriptor(partner);
const captionText = (mktFeatureType === 'English Centre' ? angle.captionTemplateEnglishCentre : angle.captionTemplateUniversity).replace(/\{\{DESC\}\}/g, desc);
document.getElementById('mktCaption').value = captionText;
const parts = splitCaptionParts(captionText);
const scene = mktFeatureType === 'English Centre' ? angle.imageSceneEnglishCentre : angle.imageSceneUniversity;
const valueText = parts.intro + ' Our placement service is 100% FREE for students — always.';
const slides = buildCarouselSlides(scene, angle.header, valueText, parts.cta);
document.getElementById('mktSlidesGrid').innerHTML = slides.map((s, i) => renderSlideBlock('mkt', i, s)).join('');
document.getElementById('mktAudio').value = 'Upbeat, optimistic corporate/lifestyle instrumental — pick a royalty-free "inspiring" or "feel-good" track from the platform\'s own audio library rather than a copyrighted trending song, so it never gets muted.';
const optionNum = (mktRegenCounter % CONTENT_CALENDAR.length) + 1;
const note = document.getElementById('mktFeaturedNote');
note.textContent = (partner
? `Featuring: ${sectorLabel(partner)} in ${partner.location || 'Malaysia'} — name kept private, category + city only.`
: `No ${mktFeatureType.toLowerCase()} partners on file yet — using a generic placeholder until you add one.`)
+ ` · Option ${optionNum} of ${CONTENT_CALENDAR.length} available today.`;
[['mktTypeUniBtn', 'University'], ['mktTypeEcBtn', 'English Centre']].forEach(([id, t]) => {
const btn = document.getElementById(id);
const active = mktFeatureType === t;
btn.className = 'text-xs font-bold px-3 py-1.5 rounded-lg transition ' + (active ? 'bg-[#10233B] text-white' : 'bg-gray-100 text-[#10233B] hover:bg-gray-200');
});
const aestheticLabel = document.getElementById('mktAestheticLabel');
if (aestheticLabel) aestheticLabel.textContent = CAROUSEL_AESTHETICS[carouselAestheticIdx].label;
}
function setMktFeatureType(t) {
mktFeatureType = t;
mktRegenCounter = 0;
renderContentGenerator();
}
function regenerateMktContent() {
mktRegenCounter++;
renderContentGenerator();
}
function initMarketingTab() {
loadMarketingDaily();
renderPlaybooks();
renderCallTimeTable();
renderContentCalendar();
renderContentGenerator();
renderAgentContentGenerator();
}
function renderCallTimeTable() {
const now = new Date();
const myt = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Kuala_Lumpur' }))
const curHour = myt.getHours();
document.getElementById('mktNowBadge').textContent = 'Now: ' + myt.toLocaleTimeString('en-MY', { hour: '2-digit', minute: '2-digit' }) + ' MYT';
document.getElementById('callTimeBody').innerHTML = CALL_TIMES.map(c => {
const rowHour = parseInt(c.time.split(':')[0], 10);
const isNow = rowHour === curHour;
return `