-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
333 lines (298 loc) · 19.1 KB
/
script.js
File metadata and controls
333 lines (298 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
import { marked } from 'marked';
import { GoogleGenerativeAI } from '@google/genai';
// --- GANTI DENGAN KUNCI API ANDA YANG VALID ---
const API_KEY = "AIzaSyDkAVtL00WxWCslXTONGyjpvLNUgySHg64";
// Fungsi untuk format angka dengan titik ribuan
function formatRupiah(angka) {
if (angka === null || angka === undefined || angka === '') return '';
let number_string = angka.toString().replace(/[^,\d]/g, ''),
split = number_string.split(','),
sisa = split[0].length % 3,
rupiah = split[0].substr(0, sisa),
ribuan = split[0].substr(sisa).match(/\d{3}/gi);
if (ribuan) {
let separator = sisa ? '.' : '';
rupiah += separator + ribuan.join('.');
}
return rupiah;
}
// Fungsi untuk menghapus format titik dari angka
function unformatRupiah(rupiah_string) {
if (!rupiah_string || typeof rupiah_string !== 'string') return 0;
return parseFloat(rupiah_string.replace(/\./g, ''));
}
document.addEventListener('DOMContentLoaded', function() {
const chartColors = {
accent: '#007AFF', accentLight: 'rgba(0, 122, 255, 0.2)', text: '#333',
grid: 'rgba(0, 0, 0, 0.05)', green: '#34C759', orange: '#FF9500',
purple: '#AF52DE', gray: '#8E8E93', red: '#FF3B30', yellow: '#FFCC00',
tokopediaColor: '#42b549',
shopeeColor: '#ee4d2d'
};
Chart.defaults.font.family = "'Poppins', sans-serif";
Chart.defaults.color = chartColors.text;
// --- LOGIKA NAVIGASI TAB ---
const navLinks = document.querySelectorAll('.nav-link');
const contentSections = document.querySelectorAll('.content-section');
function activateTab(targetId) {
navLinks.forEach(link => link.classList.remove('active'));
contentSections.forEach(section => section.classList.remove('active'));
const activeLink = document.querySelector(`.nav-link[href="#${targetId}"]`);
const activeSection = document.getElementById(targetId);
if (activeLink) activeLink.classList.add('active');
if (activeSection) activeSection.classList.add('active');
}
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
window.location.hash = targetId;
activateTab(targetId);
});
});
if (window.location.hash) {
const initialTab = window.location.hash.substring(1);
activateTab(initialTab);
} else {
activateTab('landscape');
}
// --- INISIALISASI SEMUA GRAFIK ---
new Chart(document.getElementById('gmvChart'), {
type: 'line',
data: {
labels: ['Q4 2023', 'Q2 2024', 'Q4 2024', 'Q2 2025 (Est.)'],
datasets: [{ label: 'GMV (US$ Miliar)', data: [75, 82, 88, 95], borderColor: chartColors.accent, backgroundColor: chartColors.accentLight, fill: true, tension: 0.4 }]
},
options: { maintainAspectRatio: false, responsive: true, plugins: { legend: { display: false } }, scales: { y: { border: { display: false } }, x: { border: { display: false } } } }
});
new Chart(document.getElementById('marketShareChart'), {
type: 'doughnut',
data: {
labels: ['TikTok-Tokopedia', 'Shopee', 'Lazada', 'Blibli', 'Lainnya'],
datasets: [{
label: 'Pangsa Pasar GMV',
data: [41, 40, 9, 4, 6],
backgroundColor: [ chartColors.tokopediaColor, chartColors.shopeeColor, chartColors.orange, chartColors.purple, chartColors.gray ],
borderColor: '#f8f7f4', borderWidth: 4, hoverOffset: 8
}]
},
options: { maintainAspectRatio: false, responsive: true, plugins: { legend: { display: true, position: 'bottom', labels: { padding: 15 } } } }
});
let budgetChart;
function updateBudgetChart() {
const budget = unformatRupiah(document.getElementById('marketingBudget').value) || 0;
const allocations = { video: budget * 0.4, kol: budget * 0.3, promo: budget * 0.2, other: budget * 0.1 };
const budgetChartEl = document.getElementById('budgetChart');
if (!budgetChartEl) return;
if (budgetChart) {
budgetChart.data.datasets[0].data = Object.values(allocations);
budgetChart.update();
} else {
budgetChart = new Chart(budgetChartEl, {
type: 'pie',
data: {
labels: ['Video Content & Ads', 'KOL & Afiliasi', 'Promosi & Diskon', 'Lainnya'],
datasets: [{ data: Object.values(allocations), backgroundColor: [chartColors.accent, chartColors.green, chartColors.orange, chartColors.gray] }]
},
options: { maintainAspectRatio: false, responsive: true, plugins: { legend: { display: true, position: 'bottom' } } }
});
}
}
const marketingBudgetEl = document.getElementById('marketingBudget');
if(marketingBudgetEl) { marketingBudgetEl.addEventListener('input', updateBudgetChart); }
updateBudgetChart();
new Chart(document.getElementById('sentimentChart'), {
type: 'bar',
data: {
labels: ['Harga', 'Kualitas', 'Keaslian', 'Pengiriman'],
datasets: [
{ label: 'Positif', data: [40, 75, 85, 55], backgroundColor: chartColors.green },
{ label: 'Netral', data: [30, 15, 10, 20], backgroundColor: chartColors.gray },
{ label: 'Negatif', data: [30, 10, 5, 25], backgroundColor: chartColors.red },
]
},
options: { maintainAspectRatio: false, responsive: true, scales: { x: { stacked: true }, y: { stacked: true } }, plugins: { legend: { display: false } } }
});
const competitorData = [
{ x: 3, y: 3, label: 'Hanasui', info: '<strong>Hanasui:</strong> Pemain volume dengan harga sangat kompetitif. Kuat di segmen pemula.' },
{ x: 5, y: 6, label: 'Wardah', info: '<strong>Wardah:</strong> Brand lokal raksasa dengan kepercayaan tinggi dan jangkauan luas. Harga terjangkau dengan kualitas terjamin.' },
{ x: 6, y: 5, label: 'Glad2Glow', info: '<strong>Glad2Glow:</strong> Viral di TikTok dengan harga agresif dan kemasan menarik. Fokus pada tren.' },
{ x: 8, y: 8, label: 'Skintific', info: '<strong>Skintific:</strong> Pemimpin pasar dengan fokus pada formulasi berbasis sains dan branding premium (masstige).' }
];
new Chart(document.getElementById('competitorMap'), {
type: 'scatter',
data: {
datasets: [{ label: 'Kompetitor Skincare', data: competitorData, backgroundColor: chartColors.accent, pointRadius: 8, pointHoverRadius: 10 }]
},
options: {
maintainAspectRatio: false, responsive: true,
scales: {
x: { title: { display: true, text: 'Harga (Relatif Rendah ke Tinggi)' } },
y: { title: { display: true, text: 'Kualitas & Inovasi (Persepsi)' } }
},
plugins: { legend: { display: false } },
onClick: (e, elements) => {
if (elements.length > 0) {
document.getElementById('competitorInfo').innerHTML = competitorData[elements[0].index].info;
}
}
}
});
// --- LOGIKA KALKULATOR & LAB STRATEGI ---
const labSection = document.getElementById('lab');
const formatter = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 });
function calculateAll() {
const hargaJual = unformatRupiah(document.getElementById('hargaJual').value);
const hpp = unformatRupiah(document.getElementById('hpp').value);
const biayaIklan = unformatRupiah(document.getElementById('biayaIklan').value);
const fixedCosts = unformatRupiah(document.getElementById('fixedCosts').value);
const avgPurchaseValue = unformatRupiah(document.getElementById('avgPurchaseValue').value);
const biayaPlatformPersen = parseFloat(document.getElementById('biayaPlatform').value) || 0;
const avgSales = parseFloat(document.getElementById('avgSales').value) || 0;
const purchaseFrequency = parseFloat(document.getElementById('purchaseFrequency').value) || 0;
const customerLifespan = parseFloat(document.getElementById('customerLifespan').value) || 0;
const biayaPlatform = hargaJual * (biayaPlatformPersen / 100);
const marginPerUnit = hargaJual - hpp - biayaIklan - biayaPlatform;
const netMargin = hargaJual > 0 ? (marginPerUnit / hargaJual) * 100 : 0;
const bepUnits = marginPerUnit > 0 ? Math.ceil(fixedCosts / marginPerUnit) : 0;
const annualRevenue = avgSales * hargaJual * 12;
const annualProfit = (avgSales * marginPerUnit * 12) - (fixedCosts * 12);
const ltv = avgPurchaseValue * purchaseFrequency * customerLifespan;
const totalAdSpend = biayaIklan * avgSales * 12;
const roas = totalAdSpend > 0 ? annualRevenue / totalAdSpend : 0;
document.getElementById('netMargin').textContent = `${netMargin.toFixed(1)}%`;
document.getElementById('netMargin').style.color = netMargin >= 0 ? chartColors.green : chartColors.red;
document.getElementById('bepResult').innerHTML = `Anda perlu menjual <span class="text-2xl accent-color">${bepUnits.toLocaleString('id-ID')}</span> unit/bulan untuk BEP.`;
document.getElementById('revenueResult').innerHTML = `Proyeksi Pendapatan Tahunan: <br><span class="text-2xl accent-color">${formatter.format(annualRevenue)}</span>`;
document.getElementById('ltvResult').innerHTML = `Estimasi LTV per Pelanggan: <br><span class="text-2xl accent-color">${formatter.format(ltv)}</span>`;
document.getElementById('finalRevenue').textContent = formatter.format(annualRevenue);
document.getElementById('finalProfit').textContent = formatter.format(annualProfit);
document.getElementById('finalROAS').textContent = `${roas.toFixed(2)}x`;
document.getElementById('finalProfit').style.color = annualProfit >= 0 ? chartColors.green : chartColors.red;
const verdictEl = document.getElementById('strategicVerdict');
if (netMargin > 20 && roas > 5) {
verdictEl.innerHTML = `<strong class="text-green-600">Sangat Solid.</strong> Profitabilitas sehat dan ROAS kuat. Ini adalah rencana untuk dominasi pasar.`;
} else if (netMargin > 10 && roas > 3) {
verdictEl.innerHTML = `<strong class="text-yellow-600">Potensial.</strong> Model bisnis ini bisa berjalan, tapi perlu optimalisasi biaya iklan dan operasional.`;
} else if (annualRevenue > 0) {
verdictEl.innerHTML = `<strong class="text-red-600">Peringatan Kritis.</strong> Margin tipis dan ROAS rendah, ini adalah resep bakar uang. Bongkar total struktur harga dan biaya Anda.`;
} else {
verdictEl.innerHTML = `Masukkan data di Laboratorium Strategi untuk melihat analisis akhir.`;
}
}
const rupiahInputs = ['hargaJual', 'hpp', 'biayaIklan', 'fixedCosts', 'avgPurchaseValue', 'marketingBudget', 'ai-harga-jual', 'ai-hpp', 'ai-biaya-lain'];
rupiahInputs.forEach(id => {
const inputElement = document.getElementById(id);
if (inputElement) {
inputElement.addEventListener('keyup', function(e) { this.value = formatRupiah(this.value); });
}
});
if (labSection) { labSection.addEventListener('input', calculateAll); }
const businessModelForm = document.getElementById('businessModelForm');
if (businessModelForm) {
businessModelForm.addEventListener('change', () => {
const margin = businessModelForm.querySelector('input[name="margin"]:checked')?.value;
const branding = businessModelForm.querySelector('input[name="branding"]:checked')?.value;
const resultEl = document.getElementById('businessModelResult');
if (margin && branding) {
if (margin === 'high' && branding === 'strong') { resultEl.innerHTML = `<span class="text-green-600">Rekomendasi: Jalur B (Nilai).</span> Fokus pada Lazada/Blibli.`; }
else if (margin === 'low' && branding === 'new') { resultEl.innerHTML = `<span class="accent-color">Rekomendasi: Jalur A (Volume).</span> Fokus pada TikTok-Tokopedia/Shopee.`; }
else { resultEl.innerHTML = `<span class="text-orange-500">Hybrid.</span> Perlu strategi cerdas di kedua jalur.`; }
}
});
}
const opportunityForm = document.getElementById('opportunityForm');
if (opportunityForm) {
opportunityForm.addEventListener('input', () => {
const branding = parseInt(document.getElementById('brandingScore').value);
const agility = parseInt(document.getElementById('agilityScore').value);
const marketing = parseInt(document.getElementById('marketingScore').value);
const score = Math.round(((branding * 4) + (agility * 3) + (marketing * 3)));
document.getElementById('opportunityResult').textContent = `${score * 10}/100`;
});
}
const checklist = document.getElementById('readinessChecklist');
if (checklist) {
checklist.addEventListener('change', () => {
const checkboxes = checklist.querySelectorAll('input[type="checkbox"]');
const checked = checklist.querySelectorAll('input[type="checkbox"]:checked');
const percentage = (checked.length / checkboxes.length) * 100;
document.getElementById('readinessProgress').style.width = `${percentage}%`;
document.getElementById('readinessText').textContent = `Tingkat Kesiapan: ${Math.round(percentage)}%`;
});
}
calculateAll();
// --- LOGIKA AI ANALYST ---
const analyzeButton = document.getElementById('analyze-button');
const outputDiv = document.getElementById('ai-analysis-output');
if (analyzeButton) {
analyzeButton.addEventListener('click', async () => {
if (!API_KEY || API_KEY.includes("GANTI_DENGAN")) {
outputDiv.innerHTML = `<p class="text-red-500 font-bold">Error: API_KEY tidak dikonfigurasi. Harap masukkan API Key Anda yang valid.</p>`;
return;
}
const namaProduk = document.getElementById('ai-nama-produk').value;
const hargaJual = unformatRupiah(document.getElementById('ai-harga-jual').value);
const terjualBulanan = parseFloat(document.getElementById('ai-terjual-bulanan').value);
const hpp = unformatRupiah(document.getElementById('ai-hpp').value);
const biayaLain = unformatRupiah(document.getElementById('ai-biaya-lain').value);
const strategi = document.getElementById('ai-strategi').value;
if (!namaProduk || !hargaJual || !terjualBulanan || !hpp || !biayaLain) {
outputDiv.innerHTML = `<p class="text-orange-500 font-bold">Harap isi semua kolom data produk untuk analisis yang akurat.</p>`;
return;
}
outputDiv.innerHTML = `<p class="text-gray-500 animate-pulse">Menganalisis data dengan AI...</p>`;
analyzeButton.disabled = true;
analyzeButton.classList.add('opacity-50', 'cursor-not-allowed');
const prompt = `
Persona:
You are a brutally intelligent financial and marketing strategist AI with a ruthless, visionary leadership mindset. You don’t just analyze — you command. Your job is to receive raw data and turn it into sharp, sarcastic, no-nonsense analysis, followed by a high-impact strategic directive. You never sugarcoat. You interrogate every number. You reveal financial weaknesses, expose operational waste, slap down fake growth, and humiliate vanity metrics. You do all that with the tone of a battle-hardened CFO who hates bullshit but knows how to lead a turnaround. Use Bahasa Indonesia with a "lo-gue" informal tone, laced with sharp, dismissive sarcasm. Keep technical terms in English (CAC, ROAS, LTV, margin).
---
DATA MENTAH YANG GUE DAPET:
- Nama Produk: ${namaProduk}
- Harga Jual: Rp ${parseFloat(hargaJual).toLocaleString('id-ID')}
- Terjual/Bulan: ${parseFloat(terjualBulanan).toLocaleString('id-ID')} unit
- HPP/Unit: Rp ${parseFloat(hpp).toLocaleString('id-ID')}
- Biaya Lain/Unit (Logistik, Admin, dll): Rp ${parseFloat(biayaLain).toLocaleString('id-ID')}
- Klaim Strategi Marketing: ${strategi || "Tidak disebutkan, mungkin malah gak ada strategi."}
---
TUGAS LO SEKARANG:
Bantai data ini. Jangan cuma laporin angka, interogasi angkanya. Beri gue analisis tajam dan perintah eksekusi.
### 1. Bongkar Profitabilitasnya
- Hitung Omset Kotor bulanan.
- Hitung semua Biaya Variabel (HPP + Biaya Lain).
- Hitung **Margin Kontribusi** per unit dan total.
- Hitung **Profit Bersih** bulanan. Jangan lupa sebutin **Net Profit Margin**-nya berapa persen. Kalau tipis, hina.
### 2. Kuliti Strategi Marketingnya
- Lihat strategi yang diklaim. Itu strategi beneran atau cuma aktivitas bakar uang?
- Kalau ada "Flash Sale" atau "Diskon", langsung tanya: Yakin margin lo gak kegerus sampe minus?
- Kalau ada "Iklan", langsung tantang: ROAS-nya berapa? Kalau gak jelas, anggap aja buang-buang duit.
### 3. Kasih Perintah Perang (Rekomendasi Strategis)
- **Prioritas #1 (URGENT):** Apa satu hal yang harus segera dihentikan atau diperbaiki biar gak rugi? (Contoh: Cut budget iklan yang boncos, stop diskon gila-gilaan).
- **Prioritas #2 (JANGKA PENDEK):** Apa langkah paling logis untuk naikin PROFIT, bukan cuma omset? (Contoh: Naikin harga sedikit, fokus ke produk dengan margin tertinggi, buat paket bundling yang cerdas).
- **Vonis Akhir:** Produk ini main di **Red Ocean** (perang harga berdarah-darah) atau **Blue Ocean** (niche sepi pemain)? Kasih alasannya berdasarkan data harga dan HPP.
---
FORMAT OUTPUT:
Gunakan Markdown. Judul pake heading (###). Poin-poin pake bullet points. Jangan ada basa-basi pembuka atau penutup yang sopan. Langsung hajar. Tutup dengan pertanyaan tajam yang bikin foundernya mikir, bukan malah senyum-senyum liat omset.
`;
try {
const genAI = new GoogleGenerativeAI(API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
const result = await model.generateContent(prompt);
const response = await result.response;
const text = response.text();
if (text) {
outputDiv.innerHTML = await marked.parse(text);
} else {
outputDiv.innerHTML = `<p class="text-orange-500 font-bold">AI tidak memberikan respons.</p>`;
}
} catch (err) {
console.error("AI Analysis Failed:", err);
outputDiv.innerHTML = `<p class="text-red-500 font-bold">Gagal menghubungi AI. Periksa konsol dan API Key Anda.</p>`;
} finally {
analyzeButton.disabled = false;
analyzeButton.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
}
});