Spaces:
Running
Running
Update index.html
Browse files- index.html +353 -589
index.html
CHANGED
@@ -10,7 +10,9 @@
|
|
10 |
margin: 0;
|
11 |
padding: 0;
|
12 |
box-sizing: border-box;
|
13 |
-
|
|
|
|
|
14 |
|
15 |
body {
|
16 |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
@@ -101,197 +103,12 @@
|
|
101 |
to { opacity: 1; }
|
102 |
}
|
103 |
|
104 |
-
.methods-grid {
|
105 |
-
display: grid;
|
106 |
-
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
107 |
-
gap: 2rem;
|
108 |
-
margin-bottom: 3rem;
|
109 |
-
}
|
110 |
-
|
111 |
-
.method-card {
|
112 |
-
background: rgba(255, 255, 255, 0.05);
|
113 |
-
backdrop-filter: blur(10px);
|
114 |
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
115 |
-
border-radius: 20px;
|
116 |
-
padding: 2rem;
|
117 |
-
transition: all 0.3s ease;
|
118 |
-
animation: fadeInUp 0.8s ease-out;
|
119 |
-
position: relative;
|
120 |
-
overflow: hidden;
|
121 |
-
}
|
122 |
-
|
123 |
-
.method-card::before {
|
124 |
-
content: '';
|
125 |
-
position: absolute;
|
126 |
-
top: -50%;
|
127 |
-
left: -50%;
|
128 |
-
width: 200%;
|
129 |
-
height: 200%;
|
130 |
-
background: radial-gradient(circle, rgba(58, 123, 213, 0.1) 0%, transparent 70%);
|
131 |
-
opacity: 0;
|
132 |
-
transition: opacity 0.3s ease;
|
133 |
-
}
|
134 |
-
|
135 |
-
.method-card:hover::before {
|
136 |
-
opacity: 1;
|
137 |
-
}
|
138 |
-
|
139 |
-
.method-card:hover {
|
140 |
-
transform: translateY(-10px);
|
141 |
-
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
142 |
-
border-color: rgba(58, 123, 213, 0.5);
|
143 |
-
}
|
144 |
-
|
145 |
-
.method-header {
|
146 |
-
display: flex;
|
147 |
-
align-items: center;
|
148 |
-
margin-bottom: 1.5rem;
|
149 |
-
}
|
150 |
-
|
151 |
-
.method-icon {
|
152 |
-
width: 60px;
|
153 |
-
height: 60px;
|
154 |
-
margin-right: 1rem;
|
155 |
-
display: flex;
|
156 |
-
align-items: center;
|
157 |
-
justify-content: center;
|
158 |
-
border-radius: 15px;
|
159 |
-
font-size: 2rem;
|
160 |
-
}
|
161 |
-
|
162 |
-
.outsource-icon { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
163 |
-
.inhouse-icon { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }
|
164 |
-
.platform-icon { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }
|
165 |
-
|
166 |
-
.method-title {
|
167 |
-
font-size: 1.5rem;
|
168 |
-
font-weight: 700;
|
169 |
-
}
|
170 |
-
|
171 |
-
.method-description {
|
172 |
-
color: #a8a8b3;
|
173 |
-
margin-bottom: 1.5rem;
|
174 |
-
line-height: 1.6;
|
175 |
-
}
|
176 |
-
|
177 |
-
.cost-breakdown {
|
178 |
-
background: rgba(0, 0, 0, 0.2);
|
179 |
-
border-radius: 10px;
|
180 |
-
padding: 1rem;
|
181 |
-
margin-bottom: 1.5rem;
|
182 |
-
}
|
183 |
-
|
184 |
-
.cost-item {
|
185 |
-
display: flex;
|
186 |
-
justify-content: space-between;
|
187 |
-
margin-bottom: 0.5rem;
|
188 |
-
padding: 0.5rem 0;
|
189 |
-
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
190 |
-
}
|
191 |
-
|
192 |
-
.cost-item:last-child {
|
193 |
-
border-bottom: none;
|
194 |
-
}
|
195 |
-
|
196 |
-
.pros-cons {
|
197 |
-
display: grid;
|
198 |
-
grid-template-columns: 1fr 1fr;
|
199 |
-
gap: 1rem;
|
200 |
-
margin-top: 1.5rem;
|
201 |
-
}
|
202 |
-
|
203 |
-
.pros, .cons {
|
204 |
-
padding: 1rem;
|
205 |
-
border-radius: 10px;
|
206 |
-
font-size: 0.9rem;
|
207 |
-
}
|
208 |
-
|
209 |
-
.pros {
|
210 |
-
background: rgba(46, 213, 115, 0.1);
|
211 |
-
border: 1px solid rgba(46, 213, 115, 0.3);
|
212 |
-
}
|
213 |
-
|
214 |
-
.cons {
|
215 |
-
background: rgba(255, 71, 87, 0.1);
|
216 |
-
border: 1px solid rgba(255, 71, 87, 0.3);
|
217 |
-
}
|
218 |
-
|
219 |
-
.pros h4, .cons h4 {
|
220 |
-
margin-bottom: 0.5rem;
|
221 |
-
font-size: 1rem;
|
222 |
-
}
|
223 |
-
|
224 |
-
.pros ul, .cons ul {
|
225 |
-
list-style: none;
|
226 |
-
padding-left: 0;
|
227 |
-
}
|
228 |
-
|
229 |
-
.pros li::before {
|
230 |
-
content: "✓ ";
|
231 |
-
color: #2ed573;
|
232 |
-
font-weight: bold;
|
233 |
-
}
|
234 |
-
|
235 |
-
.cons li::before {
|
236 |
-
content: "✗ ";
|
237 |
-
color: #ff4757;
|
238 |
-
font-weight: bold;
|
239 |
-
}
|
240 |
-
|
241 |
-
.calculator-section {
|
242 |
-
background: rgba(255, 255, 255, 0.05);
|
243 |
-
backdrop-filter: blur(10px);
|
244 |
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
245 |
-
border-radius: 20px;
|
246 |
-
padding: 2rem;
|
247 |
-
margin-bottom: 3rem;
|
248 |
-
animation: fadeInUp 1s ease-out;
|
249 |
-
}
|
250 |
-
|
251 |
.calculator-title {
|
252 |
font-size: 2rem;
|
253 |
margin-bottom: 2rem;
|
254 |
text-align: center;
|
255 |
}
|
256 |
|
257 |
-
.input-group {
|
258 |
-
display: grid;
|
259 |
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
260 |
-
gap: 1.5rem;
|
261 |
-
margin-bottom: 2rem;
|
262 |
-
}
|
263 |
-
|
264 |
-
.input-field {
|
265 |
-
display: flex;
|
266 |
-
flex-direction: column;
|
267 |
-
}
|
268 |
-
|
269 |
-
.input-field label {
|
270 |
-
margin-bottom: 0.5rem;
|
271 |
-
color: #a8a8b3;
|
272 |
-
font-size: 0.9rem;
|
273 |
-
}
|
274 |
-
|
275 |
-
.input-field input, .input-field select {
|
276 |
-
background: rgba(255, 255, 255, 0.1);
|
277 |
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
278 |
-
border-radius: 10px;
|
279 |
-
padding: 0.75rem 1rem;
|
280 |
-
color: #ffffff;
|
281 |
-
font-size: 1rem;
|
282 |
-
transition: all 0.3s ease;
|
283 |
-
}
|
284 |
-
|
285 |
-
.input-field input:focus, .input-field select:focus {
|
286 |
-
outline: none;
|
287 |
-
border-color: #3a7bd5;
|
288 |
-
background: rgba(255, 255, 255, 0.15);
|
289 |
-
}
|
290 |
-
|
291 |
-
.input-field select option {
|
292 |
-
background: #24243e;
|
293 |
-
}
|
294 |
-
|
295 |
.calculate-btn {
|
296 |
background: linear-gradient(45deg, #00d2ff, #3a7bd5);
|
297 |
border: none;
|
@@ -311,34 +128,6 @@
|
|
311 |
box-shadow: 0 10px 20px rgba(58, 123, 213, 0.3);
|
312 |
}
|
313 |
|
314 |
-
.results-section {
|
315 |
-
margin-top: 3rem;
|
316 |
-
display: none;
|
317 |
-
}
|
318 |
-
|
319 |
-
.results-grid {
|
320 |
-
display: grid;
|
321 |
-
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
322 |
-
gap: 2rem;
|
323 |
-
margin-bottom: 2rem;
|
324 |
-
}
|
325 |
-
|
326 |
-
.result-card {
|
327 |
-
background: rgba(0, 0, 0, 0.3);
|
328 |
-
border-radius: 15px;
|
329 |
-
padding: 1.5rem;
|
330 |
-
text-align: center;
|
331 |
-
}
|
332 |
-
|
333 |
-
.result-value {
|
334 |
-
font-size: 2.5rem;
|
335 |
-
font-weight: 700;
|
336 |
-
margin: 1rem 0;
|
337 |
-
background: linear-gradient(45deg, #00d2ff, #3a7bd5);
|
338 |
-
-webkit-background-clip: text;
|
339 |
-
-webkit-text-fill-color: transparent;
|
340 |
-
}
|
341 |
-
|
342 |
.chart-container {
|
343 |
position: relative;
|
344 |
height: 400px;
|
@@ -396,6 +185,12 @@
|
|
396 |
position: relative;
|
397 |
overflow: hidden;
|
398 |
user-select: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
}
|
400 |
|
401 |
.option-card.selected {
|
@@ -419,12 +214,14 @@
|
|
419 |
align-items: center;
|
420 |
justify-content: center;
|
421 |
font-weight: bold;
|
|
|
422 |
}
|
423 |
|
424 |
.option-card:hover:not(.selected) {
|
425 |
border-color: #00d2ff;
|
426 |
background: rgba(255, 255, 255, 0.1);
|
427 |
transform: translateY(-2px);
|
|
|
428 |
}
|
429 |
|
430 |
.option-name {
|
@@ -458,27 +255,6 @@
|
|
458 |
color: #00d2ff;
|
459 |
}
|
460 |
|
461 |
-
.period-tabs {
|
462 |
-
display: flex;
|
463 |
-
justify-content: center;
|
464 |
-
gap: 1rem;
|
465 |
-
margin-bottom: 2rem;
|
466 |
-
}
|
467 |
-
|
468 |
-
.period-tab {
|
469 |
-
padding: 0.5rem 1.5rem;
|
470 |
-
background: rgba(255, 255, 255, 0.1);
|
471 |
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
472 |
-
border-radius: 20px;
|
473 |
-
cursor: pointer;
|
474 |
-
transition: all 0.3s ease;
|
475 |
-
}
|
476 |
-
|
477 |
-
.period-tab.active {
|
478 |
-
background: linear-gradient(45deg, #00d2ff, #3a7bd5);
|
479 |
-
border-color: transparent;
|
480 |
-
}
|
481 |
-
|
482 |
.custom-summary {
|
483 |
display: grid;
|
484 |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
@@ -618,14 +394,6 @@
|
|
618 |
font-size: 2rem;
|
619 |
}
|
620 |
|
621 |
-
.methods-grid {
|
622 |
-
grid-template-columns: 1fr;
|
623 |
-
}
|
624 |
-
|
625 |
-
.pros-cons {
|
626 |
-
grid-template-columns: 1fr;
|
627 |
-
}
|
628 |
-
|
629 |
.tabs {
|
630 |
flex-direction: column;
|
631 |
}
|
@@ -1392,360 +1160,356 @@
|
|
1392 |
<span id="platformTotal">$0K</span>
|
1393 |
</div>
|
1394 |
</div>
|
|
|
|
|
1395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1397 |
|
1398 |
-
|
1399 |
-
|
1400 |
-
|
1401 |
-
|
1402 |
-
|
1403 |
-
|
1404 |
-
|
1405 |
-
|
1406 |
-
|
1407 |
-
|
1408 |
-
|
1409 |
-
|
1410 |
-
|
1411 |
-
|
1412 |
-
|
1413 |
-
|
1414 |
-
|
1415 |
-
|
1416 |
-
|
1417 |
-
|
1418 |
-
|
1419 |
-
|
1420 |
-
|
1421 |
-
|
1422 |
-
|
1423 |
-
|
1424 |
-
|
1425 |
-
|
1426 |
-
|
1427 |
-
|
1428 |
-
|
1429 |
-
|
1430 |
-
|
1431 |
-
|
1432 |
-
|
1433 |
-
|
1434 |
-
|
1435 |
-
|
1436 |
-
|
1437 |
-
|
1438 |
-
|
1439 |
-
|
1440 |
-
|
1441 |
-
|
1442 |
-
|
1443 |
-
|
1444 |
-
|
1445 |
-
|
1446 |
-
|
1447 |
-
|
1448 |
-
|
1449 |
-
|
1450 |
-
|
1451 |
-
|
1452 |
-
|
1453 |
-
|
1454 |
-
|
1455 |
-
|
1456 |
-
|
1457 |
-
|
1458 |
-
|
1459 |
-
|
1460 |
-
|
1461 |
-
|
1462 |
-
|
1463 |
-
|
1464 |
-
|
1465 |
-
|
1466 |
-
|
1467 |
-
|
1468 |
-
|
1469 |
-
|
1470 |
-
|
1471 |
-
|
1472 |
-
|
1473 |
-
|
1474 |
-
|
1475 |
-
|
1476 |
-
|
1477 |
-
|
1478 |
-
|
1479 |
-
|
1480 |
-
|
1481 |
-
|
1482 |
-
|
1483 |
-
|
1484 |
-
|
1485 |
-
|
1486 |
-
|
1487 |
-
|
1488 |
-
|
1489 |
-
|
1490 |
-
|
1491 |
-
|
1492 |
-
|
1493 |
-
|
1494 |
-
|
1495 |
-
|
1496 |
-
|
1497 |
-
|
1498 |
-
selectedComponents.service.forEach(serviceId => {
|
1499 |
-
const card = document.querySelector(`[data-category="service"][data-id="${serviceId}"]`);
|
1500 |
-
if (!card) return;
|
1501 |
-
|
1502 |
-
const cost = parseFloat(card.dataset.cost);
|
1503 |
-
const time = parseFloat(card.dataset.time);
|
1504 |
-
const resource = parseFloat(card.dataset.resource) || 0;
|
1505 |
-
const name = card.querySelector('.option-name').textContent;
|
1506 |
-
|
1507 |
-
totalInitialCost += cost;
|
1508 |
-
totalResourceCost += resource;
|
1509 |
-
maxTime = Math.max(maxTime, time);
|
1510 |
-
|
1511 |
-
componentDetails.push({
|
1512 |
-
name: name,
|
1513 |
-
cost: cost,
|
1514 |
-
type: 'service'
|
1515 |
-
});
|
1516 |
-
});
|
1517 |
-
|
1518 |
-
// 기타 구성 요소 비용 계산
|
1519 |
-
['db', 'auth', 'credit', 'payment', 'marketing', 'monitoring'].forEach(category => {
|
1520 |
-
if (selectedComponents[category]) {
|
1521 |
-
const card = document.querySelector(`[data-category="${category}"][data-id="${selectedComponents[category]}"]`);
|
1522 |
-
if (!card) return;
|
1523 |
|
1524 |
-
|
1525 |
-
|
1526 |
-
const name = card.querySelector('.option-name').textContent;
|
1527 |
|
1528 |
-
|
1529 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1530 |
|
1531 |
-
|
1532 |
-
|
1533 |
-
|
1534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1535 |
});
|
1536 |
}
|
1537 |
-
|
1538 |
-
|
1539 |
-
|
1540 |
-
|
1541 |
-
|
1542 |
-
|
1543 |
-
|
1544 |
-
monthlySupport = parseFloat(card.dataset.cost);
|
1545 |
-
const name = card.querySelector('.option-name').textContent;
|
1546 |
|
1547 |
-
|
1548 |
-
|
1549 |
-
|
1550 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1551 |
});
|
1552 |
}
|
1553 |
-
}
|
1554 |
-
|
1555 |
-
// 추가 기능 비용 계산
|
1556 |
-
selectedComponents.additional.forEach(additionalId => {
|
1557 |
-
const card = document.querySelector(`[data-category="additional"][data-id="${additionalId}"]`);
|
1558 |
-
if (!card) return;
|
1559 |
-
|
1560 |
-
const cost = parseFloat(card.dataset.cost);
|
1561 |
-
const time = parseFloat(card.dataset.time);
|
1562 |
-
const name = card.querySelector('.option-name').textContent;
|
1563 |
-
|
1564 |
-
totalInitialCost += cost;
|
1565 |
-
maxTime = Math.max(maxTime, time);
|
1566 |
-
|
1567 |
-
componentDetails.push({
|
1568 |
-
name: name,
|
1569 |
-
cost: cost,
|
1570 |
-
type: 'additional'
|
1571 |
-
});
|
1572 |
-
});
|
1573 |
-
|
1574 |
-
// 월간 운영 비용 계산
|
1575 |
-
const serverCost = totalInitialCost > 100 ? 5 : totalInitialCost > 50 ? 3 : totalInitialCost > 20 ? 2 : 1;
|
1576 |
-
const resourceCost = totalResourceCost;
|
1577 |
-
const maintenanceCost = totalInitialCost * 0.05; // 초기 비용의 5%
|
1578 |
-
const totalMonthlyCost = serverCost + resourceCost + maintenanceCost + monthlySupport;
|
1579 |
-
|
1580 |
-
// 결과 표시
|
1581 |
-
document.getElementById('initialCost').textContent = `$${totalInitialCost}K`;
|
1582 |
-
document.getElementById('devTime').textContent = `${maxTime}개월`;
|
1583 |
-
document.getElementById('serverCost').textContent = `$${serverCost}K`;
|
1584 |
-
document.getElementById('resourceCost').textContent = `$${resourceCost.toFixed(1)}K`;
|
1585 |
-
document.getElementById('maintenanceCost').textContent = `$${maintenanceCost.toFixed(1)}K`;
|
1586 |
-
document.getElementById('totalMonthlyCost').textContent = `$${totalMonthlyCost.toFixed(1)}K`;
|
1587 |
-
|
1588 |
-
// 총 비용 계산 (기간별)
|
1589 |
-
updateTotalCost(totalInitialCost, totalMonthlyCost);
|
1590 |
-
|
1591 |
-
// 선택한 구성 요소 목록 표시
|
1592 |
-
displayComponentList(componentDetails);
|
1593 |
-
|
1594 |
-
// 구축 방식별 비교
|
1595 |
-
compareBuilderMethods(totalInitialCost, totalMonthlyCost, maxTime);
|
1596 |
-
|
1597 |
-
// 차트 업데이트
|
1598 |
-
updateCustomChart(totalInitialCost, totalMonthlyCost);
|
1599 |
-
|
1600 |
-
// 결과 섹션 표시
|
1601 |
-
document.getElementById('customResult').style.display = 'block';
|
1602 |
-
}
|
1603 |
-
|
1604 |
-
// 총 비용 업데이트
|
1605 |
-
function updateTotalCost(initialCost, monthlyCost) {
|
1606 |
-
const totalCost = initialCost + (monthlyCost * currentPeriod);
|
1607 |
-
document.getElementById('totalCost').textContent = `$${totalCost.toFixed(1)}K`;
|
1608 |
-
}
|
1609 |
-
|
1610 |
-
// 구성 요소 목록 표시
|
1611 |
-
function displayComponentList(components) {
|
1612 |
-
const listContainer = document.getElementById('componentList');
|
1613 |
-
listContainer.innerHTML = '';
|
1614 |
-
|
1615 |
-
components.forEach(component => {
|
1616 |
-
const item = document.createElement('div');
|
1617 |
-
item.className = 'component-list-item';
|
1618 |
-
|
1619 |
-
let costDisplay = component.type === 'monthly'
|
1620 |
-
? `월 $${component.cost}K`
|
1621 |
-
: `$${component.cost}K`;
|
1622 |
-
|
1623 |
-
item.innerHTML = `
|
1624 |
-
<span>${component.name}</span>
|
1625 |
-
<span style="color: #00d2ff;">${costDisplay}</span>
|
1626 |
-
`;
|
1627 |
-
|
1628 |
-
listContainer.appendChild(item);
|
1629 |
-
});
|
1630 |
-
}
|
1631 |
-
|
1632 |
-
// 구축 방식별 비교
|
1633 |
-
function compareBuilderMethods(initialCost, monthlyCost, devTime) {
|
1634 |
-
// 외주 용역 개발
|
1635 |
-
const outsourceInitial = initialCost;
|
1636 |
-
const outsourceMonthly = monthlyCost;
|
1637 |
-
const outsourceTotal = outsourceInitial + (outsourceMonthly * currentPeriod);
|
1638 |
-
|
1639 |
-
// 자체 인력 개발
|
1640 |
-
const developerSalary = 10; // 월 $10K
|
1641 |
-
const teamSize = Math.ceil(initialCost / 50); // $50K당 1명
|
1642 |
-
const inhouseDevTime = Math.max(devTime * 1.5, 3); // 최소 3개월
|
1643 |
-
const inhouseInitial = developerSalary * teamSize * inhouseDevTime;
|
1644 |
-
const inhouseMonthly = monthlyCost + (developerSalary * Math.ceil(teamSize / 2)); // 유지보수 인력
|
1645 |
-
const inhouseTotal = inhouseInitial + (inhouseMonthly * currentPeriod);
|
1646 |
-
|
1647 |
-
// 플랫폼 대여
|
1648 |
-
const platformInitial = 1; // 설정 비용
|
1649 |
-
const platformMonthly = monthlyCost * 2 + 5; // 플랫폼 수수료 포함
|
1650 |
-
const platformTotal = platformInitial + (platformMonthly * currentPeriod);
|
1651 |
-
|
1652 |
-
// 결과 표시
|
1653 |
-
document.getElementById('outsourceInitial').textContent = `$${outsourceInitial}K`;
|
1654 |
-
document.getElementById('outsourceMonthly').textContent = `$${outsourceMonthly.toFixed(1)}K`;
|
1655 |
-
document.getElementById('outsourceTotal').textContent = `$${outsourceTotal.toFixed(1)}K`;
|
1656 |
-
|
1657 |
-
document.getElementById('inhouseInitial').textContent = `$${inhouseInitial}K`;
|
1658 |
-
document.getElementById('inhouseMonthly').textContent = `$${inhouseMonthly.toFixed(1)}K`;
|
1659 |
-
document.getElementById('inhouseTotal').textContent = `$${inhouseTotal.toFixed(1)}K`;
|
1660 |
-
|
1661 |
-
document.getElementById('platformInitial').textContent = `$${platformInitial}K`;
|
1662 |
-
document.getElementById('platformMonthly').textContent = `$${platformMonthly.toFixed(1)}K`;
|
1663 |
-
document.getElementById('platformTotal').textContent = `$${platformTotal.toFixed(1)}K`;
|
1664 |
-
}
|
1665 |
-
|
1666 |
-
// 차트 업데이트
|
1667 |
-
function updateCustomChart(initialCost, monthlyCost) {
|
1668 |
-
const ctx = document.getElementById('customChart').getContext('2d');
|
1669 |
-
|
1670 |
-
if (customChart) {
|
1671 |
-
customChart.destroy();
|
1672 |
-
}
|
1673 |
-
|
1674 |
-
const months = Array.from({length: 12}, (_, i) => `${i + 1}개월`);
|
1675 |
-
const outsourceData = months.map((_, i) => initialCost + (monthlyCost * (i + 1)));
|
1676 |
-
const inhouseData = months.map((_, i) => {
|
1677 |
-
const teamSize = Math.ceil(initialCost / 50);
|
1678 |
-
const inhouseInitial = 10 * teamSize * 3;
|
1679 |
-
const inhouseMonthly = monthlyCost + (10 * Math.ceil(teamSize / 2));
|
1680 |
-
return inhouseInitial + (inhouseMonthly * (i + 1));
|
1681 |
-
});
|
1682 |
-
const platformData = months.map((_, i) => 1 + ((monthlyCost * 2 + 5) * (i + 1)));
|
1683 |
-
|
1684 |
-
customChart = new Chart(ctx, {
|
1685 |
-
type: 'line',
|
1686 |
-
data: {
|
1687 |
-
labels: months,
|
1688 |
-
datasets: [{
|
1689 |
-
label: '외주 용역 개발',
|
1690 |
-
data: outsourceData,
|
1691 |
-
borderColor: '#764ba2',
|
1692 |
-
backgroundColor: 'rgba(118, 75, 162, 0.1)',
|
1693 |
-
tension: 0.4
|
1694 |
-
}, {
|
1695 |
-
label: '자체 인력 개발',
|
1696 |
-
data: inhouseData,
|
1697 |
-
borderColor: '#f5576c',
|
1698 |
-
backgroundColor: 'rgba(245, 87, 108, 0.1)',
|
1699 |
-
tension: 0.4
|
1700 |
-
}, {
|
1701 |
-
label: '플랫폼 대여',
|
1702 |
-
data: platformData,
|
1703 |
-
borderColor: '#4facfe',
|
1704 |
-
backgroundColor: 'rgba(79, 172, 254, 0.1)',
|
1705 |
-
tension: 0.4
|
1706 |
-
}]
|
1707 |
-
},
|
1708 |
-
options: {
|
1709 |
-
responsive: true,
|
1710 |
-
maintainAspectRatio: false,
|
1711 |
-
plugins: {
|
1712 |
-
legend: {
|
1713 |
-
labels: {
|
1714 |
-
color: '#ffffff'
|
1715 |
-
}
|
1716 |
-
},
|
1717 |
-
tooltip: {
|
1718 |
-
callbacks: {
|
1719 |
-
label: function(context) {
|
1720 |
-
return context.dataset.label + ': $' + context.parsed.y.toFixed(1) + 'K';
|
1721 |
-
}
|
1722 |
-
}
|
1723 |
-
}
|
1724 |
-
},
|
1725 |
-
scales: {
|
1726 |
-
x: {
|
1727 |
-
grid: {
|
1728 |
-
color: 'rgba(255, 255, 255, 0.1)'
|
1729 |
-
},
|
1730 |
-
ticks: {
|
1731 |
-
color: '#a8a8b3'
|
1732 |
-
}
|
1733 |
-
},
|
1734 |
-
y: {
|
1735 |
-
grid: {
|
1736 |
-
color: 'rgba(255, 255, 255, 0.1)'
|
1737 |
-
},
|
1738 |
-
ticks: {
|
1739 |
-
color: '#a8a8b3',
|
1740 |
-
callback: function(value) {
|
1741 |
-
return '$' + value + 'K';
|
1742 |
-
}
|
1743 |
-
}
|
1744 |
-
}
|
1745 |
-
}
|
1746 |
-
}
|
1747 |
-
});
|
1748 |
-
}
|
1749 |
-
</script>
|
1750 |
-
</body>
|
1751 |
-
</html>
|
|
|
10 |
margin: 0;
|
11 |
padding: 0;
|
12 |
box-sizing: border-box;
|
13 |
+
</script>
|
14 |
+
</body>
|
15 |
+
</html>
|
16 |
|
17 |
body {
|
18 |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
103 |
to { opacity: 1; }
|
104 |
}
|
105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
.calculator-title {
|
107 |
font-size: 2rem;
|
108 |
margin-bottom: 2rem;
|
109 |
text-align: center;
|
110 |
}
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
.calculate-btn {
|
113 |
background: linear-gradient(45deg, #00d2ff, #3a7bd5);
|
114 |
border: none;
|
|
|
128 |
box-shadow: 0 10px 20px rgba(58, 123, 213, 0.3);
|
129 |
}
|
130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
.chart-container {
|
132 |
position: relative;
|
133 |
height: 400px;
|
|
|
185 |
position: relative;
|
186 |
overflow: hidden;
|
187 |
user-select: none;
|
188 |
+
pointer-events: auto;
|
189 |
+
}
|
190 |
+
|
191 |
+
/* 중요: 하위 요소들이 클릭 이벤트를 방해하지 않도록 설정 */
|
192 |
+
.option-card * {
|
193 |
+
pointer-events: none;
|
194 |
}
|
195 |
|
196 |
.option-card.selected {
|
|
|
214 |
align-items: center;
|
215 |
justify-content: center;
|
216 |
font-weight: bold;
|
217 |
+
pointer-events: none;
|
218 |
}
|
219 |
|
220 |
.option-card:hover:not(.selected) {
|
221 |
border-color: #00d2ff;
|
222 |
background: rgba(255, 255, 255, 0.1);
|
223 |
transform: translateY(-2px);
|
224 |
+
z-index: 10;
|
225 |
}
|
226 |
|
227 |
.option-name {
|
|
|
255 |
color: #00d2ff;
|
256 |
}
|
257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
.custom-summary {
|
259 |
display: grid;
|
260 |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
394 |
font-size: 2rem;
|
395 |
}
|
396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
397 |
.tabs {
|
398 |
flex-direction: column;
|
399 |
}
|
|
|
1160 |
<span id="platformTotal">$0K</span>
|
1161 |
</div>
|
1162 |
</div>
|
1163 |
+
</div>
|
1164 |
+
</div>
|
1165 |
|
1166 |
+
<!-- 기간별 비용 차트 -->
|
1167 |
+
<div class="chart-container">
|
1168 |
+
<canvas id="customChart"></canvas>
|
1169 |
+
</div>
|
1170 |
+
</div>
|
1171 |
+
</div>
|
1172 |
+
</div>
|
1173 |
+
</div>
|
1174 |
+
|
1175 |
+
<script>
|
1176 |
+
// 전역 변수
|
1177 |
+
let selectedComponents = {
|
1178 |
+
service: [],
|
1179 |
+
db: null,
|
1180 |
+
auth: null,
|
1181 |
+
credit: null,
|
1182 |
+
payment: null,
|
1183 |
+
marketing: null,
|
1184 |
+
support: null,
|
1185 |
+
monitoring: null,
|
1186 |
+
additional: []
|
1187 |
+
};
|
1188 |
+
|
1189 |
+
let customChart = null;
|
1190 |
+
let currentPeriod = 3;
|
1191 |
+
|
1192 |
+
// 페이지 로드 시 이벤트 리스너 등록
|
1193 |
+
document.addEventListener('DOMContentLoaded', function() {
|
1194 |
+
console.log('DOM loaded, registering click events...'); // 디버깅용
|
1195 |
+
|
1196 |
+
// 모든 옵션 카드에 직접 이벤트 리스너 추가
|
1197 |
+
const optionCards = document.querySelectorAll('.option-card');
|
1198 |
+
console.log('Found option cards:', optionCards.length); // 디버깅용
|
1199 |
+
|
1200 |
+
optionCards.forEach(card => {
|
1201 |
+
card.addEventListener('click', function(e) {
|
1202 |
+
e.preventDefault();
|
1203 |
+
e.stopPropagation();
|
1204 |
+
|
1205 |
+
const category = this.dataset.category;
|
1206 |
+
const id = this.dataset.id;
|
1207 |
+
|
1208 |
+
console.log('Card clicked:', category, id); // 디버깅용
|
1209 |
+
|
1210 |
+
if (!category || !id) {
|
1211 |
+
console.error('Missing category or id:', category, id);
|
1212 |
+
return;
|
1213 |
+
}
|
1214 |
+
|
1215 |
+
if (category === 'service' || category === 'additional') {
|
1216 |
+
// 복수 선택 가능
|
1217 |
+
this.classList.toggle('selected');
|
1218 |
+
|
1219 |
+
if (this.classList.contains('selected')) {
|
1220 |
+
if (!selectedComponents[category].includes(id)) {
|
1221 |
+
selectedComponents[category].push(id);
|
1222 |
+
}
|
1223 |
+
} else {
|
1224 |
+
const index = selectedComponents[category].indexOf(id);
|
1225 |
+
if (index > -1) {
|
1226 |
+
selectedComponents[category].splice(index, 1);
|
1227 |
+
}
|
1228 |
+
}
|
1229 |
+
} else {
|
1230 |
+
// 단일 선택
|
1231 |
+
document.querySelectorAll(`[data-category="${category}"]`).forEach(c => {
|
1232 |
+
c.classList.remove('selected');
|
1233 |
+
});
|
1234 |
+
|
1235 |
+
this.classList.add('selected');
|
1236 |
+
selectedComponents[category] = id;
|
1237 |
+
}
|
1238 |
+
|
1239 |
+
console.log('Selected components:', selectedComponents); // 디버깅용
|
1240 |
+
});
|
1241 |
+
});
|
1242 |
+
});
|
1243 |
|
1244 |
+
// 탭 전환 함수
|
1245 |
+
function switchTab(tabId) {
|
1246 |
+
document.querySelectorAll('.tab').forEach(tab => {
|
1247 |
+
tab.classList.remove('active');
|
1248 |
+
});
|
1249 |
+
document.querySelectorAll('.tab-content').forEach(content => {
|
1250 |
+
content.classList.remove('active');
|
1251 |
+
});
|
1252 |
+
|
1253 |
+
event.target.classList.add('active');
|
1254 |
+
document.getElementById(tabId).classList.add('active');
|
1255 |
+
}
|
1256 |
|
1257 |
+
// 커스텀 비용 계산 함수
|
1258 |
+
function calculateCustomCost() {
|
1259 |
+
let totalInitialCost = 0;
|
1260 |
+
let totalResourceCost = 0;
|
1261 |
+
let maxTime = 0;
|
1262 |
+
let componentDetails = [];
|
1263 |
+
|
1264 |
+
// 서비스 비용 계산
|
1265 |
+
selectedComponents.service.forEach(serviceId => {
|
1266 |
+
const card = document.querySelector(`[data-category="service"][data-id="${serviceId}"]`);
|
1267 |
+
if (!card) return;
|
1268 |
+
|
1269 |
+
const cost = parseFloat(card.dataset.cost);
|
1270 |
+
const time = parseFloat(card.dataset.time);
|
1271 |
+
const resource = parseFloat(card.dataset.resource) || 0;
|
1272 |
+
const name = card.querySelector('.option-name').textContent;
|
1273 |
+
|
1274 |
+
totalInitialCost += cost;
|
1275 |
+
totalResourceCost += resource;
|
1276 |
+
maxTime = Math.max(maxTime, time);
|
1277 |
+
|
1278 |
+
componentDetails.push({
|
1279 |
+
name: name,
|
1280 |
+
cost: cost,
|
1281 |
+
type: 'service'
|
1282 |
+
});
|
1283 |
+
});
|
1284 |
+
|
1285 |
+
// 기타 구성 요소 비용 계산
|
1286 |
+
['db', 'auth', 'credit', 'payment', 'marketing', 'monitoring'].forEach(category => {
|
1287 |
+
if (selectedComponents[category]) {
|
1288 |
+
const card = document.querySelector(`[data-category="${category}"][data-id="${selectedComponents[category]}"]`);
|
1289 |
+
if (!card) return;
|
1290 |
+
|
1291 |
+
const cost = parseFloat(card.dataset.cost);
|
1292 |
+
const time = parseFloat(card.dataset.time);
|
1293 |
+
const name = card.querySelector('.option-name').textContent;
|
1294 |
+
|
1295 |
+
totalInitialCost += cost;
|
1296 |
+
maxTime = Math.max(maxTime, time);
|
1297 |
+
|
1298 |
+
componentDetails.push({
|
1299 |
+
name: name,
|
1300 |
+
cost: cost,
|
1301 |
+
type: category
|
1302 |
+
});
|
1303 |
+
}
|
1304 |
+
});
|
1305 |
+
|
1306 |
+
// 지원 서비스 (월간 비용)
|
1307 |
+
let monthlySupport = 0;
|
1308 |
+
if (selectedComponents.support) {
|
1309 |
+
const card = document.querySelector(`[data-category="support"][data-id="${selectedComponents.support]}"]`);
|
1310 |
+
if (card) {
|
1311 |
+
monthlySupport = parseFloat(card.dataset.cost);
|
1312 |
+
const name = card.querySelector('.option-name').textContent;
|
1313 |
+
|
1314 |
+
componentDetails.push({
|
1315 |
+
name: name,
|
1316 |
+
cost: monthlySupport,
|
1317 |
+
type: 'monthly'
|
1318 |
+
});
|
1319 |
+
}
|
1320 |
+
}
|
1321 |
+
|
1322 |
+
// 추가 기능 비용 계산
|
1323 |
+
selectedComponents.additional.forEach(additionalId => {
|
1324 |
+
const card = document.querySelector(`[data-category="additional"][data-id="${additionalId}"]`);
|
1325 |
+
if (!card) return;
|
1326 |
+
|
1327 |
+
const cost = parseFloat(card.dataset.cost);
|
1328 |
+
const time = parseFloat(card.dataset.time);
|
1329 |
+
const name = card.querySelector('.option-name').textContent;
|
1330 |
+
|
1331 |
+
totalInitialCost += cost;
|
1332 |
+
maxTime = Math.max(maxTime, time);
|
1333 |
+
|
1334 |
+
componentDetails.push({
|
1335 |
+
name: name,
|
1336 |
+
cost: cost,
|
1337 |
+
type: 'additional'
|
1338 |
+
});
|
1339 |
+
});
|
1340 |
+
|
1341 |
+
// 월간 운영 비용 계산
|
1342 |
+
const serverCost = totalInitialCost > 100 ? 5 : totalInitialCost > 50 ? 3 : totalInitialCost > 20 ? 2 : 1;
|
1343 |
+
const resourceCost = totalResourceCost;
|
1344 |
+
const maintenanceCost = totalInitialCost * 0.05; // 초기 비용의 5%
|
1345 |
+
const totalMonthlyCost = serverCost + resourceCost + maintenanceCost + monthlySupport;
|
1346 |
+
|
1347 |
+
// 결과 표시
|
1348 |
+
document.getElementById('initialCost').textContent = `${totalInitialCost}K`;
|
1349 |
+
document.getElementById('devTime').textContent = `${maxTime}개월`;
|
1350 |
+
document.getElementById('serverCost').textContent = `${serverCost}K`;
|
1351 |
+
document.getElementById('resourceCost').textContent = `${resourceCost.toFixed(1)}K`;
|
1352 |
+
document.getElementById('maintenanceCost').textContent = `${maintenanceCost.toFixed(1)}K`;
|
1353 |
+
document.getElementById('totalMonthlyCost').textContent = `${totalMonthlyCost.toFixed(1)}K`;
|
1354 |
+
|
1355 |
+
// 총 비용 계산 (기간별)
|
1356 |
+
updateTotalCost(totalInitialCost, totalMonthlyCost);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1357 |
|
1358 |
+
// 선택한 구성 요소 목록 표시
|
1359 |
+
displayComponentList(componentDetails);
|
|
|
1360 |
|
1361 |
+
// 구축 방식별 비교
|
1362 |
+
compareBuilderMethods(totalInitialCost, totalMonthlyCost, maxTime);
|
1363 |
+
|
1364 |
+
// 차트 업데이트
|
1365 |
+
updateCustomChart(totalInitialCost, totalMonthlyCost);
|
1366 |
+
|
1367 |
+
// 결과 섹션 표시
|
1368 |
+
document.getElementById('customResult').style.display = 'block';
|
1369 |
+
}
|
1370 |
+
|
1371 |
+
// 총 비용 업데이트
|
1372 |
+
function updateTotalCost(initialCost, monthlyCost) {
|
1373 |
+
const totalCost = initialCost + (monthlyCost * currentPeriod);
|
1374 |
+
document.getElementById('totalCost').textContent = `${totalCost.toFixed(1)}K`;
|
1375 |
+
}
|
1376 |
+
|
1377 |
+
// 구성 요소 목록 표시
|
1378 |
+
function displayComponentList(components) {
|
1379 |
+
const listContainer = document.getElementById('componentList');
|
1380 |
+
listContainer.innerHTML = '';
|
1381 |
|
1382 |
+
components.forEach(component => {
|
1383 |
+
const item = document.createElement('div');
|
1384 |
+
item.className = 'component-list-item';
|
1385 |
+
|
1386 |
+
let costDisplay = component.type === 'monthly'
|
1387 |
+
? `월 ${component.cost}K`
|
1388 |
+
: `${component.cost}K`;
|
1389 |
+
|
1390 |
+
item.innerHTML = `
|
1391 |
+
<span>${component.name}</span>
|
1392 |
+
<span style="color: #00d2ff;">${costDisplay}</span>
|
1393 |
+
`;
|
1394 |
+
|
1395 |
+
listContainer.appendChild(item);
|
1396 |
});
|
1397 |
}
|
1398 |
+
|
1399 |
+
// 구축 방식별 비교
|
1400 |
+
function compareBuilderMethods(initialCost, monthlyCost, devTime) {
|
1401 |
+
// 외주 용역 개발
|
1402 |
+
const outsourceInitial = initialCost;
|
1403 |
+
const outsourceMonthly = monthlyCost;
|
1404 |
+
const outsourceTotal = outsourceInitial + (outsourceMonthly * currentPeriod);
|
|
|
|
|
1405 |
|
1406 |
+
// 자체 인력 개발
|
1407 |
+
const developerSalary = 10; // 월 $10K
|
1408 |
+
const teamSize = Math.ceil(initialCost / 50); // $50K당 1명
|
1409 |
+
const inhouseDevTime = Math.max(devTime * 1.5, 3); // 최소 3개월
|
1410 |
+
const inhouseInitial = developerSalary * teamSize * inhouseDevTime;
|
1411 |
+
const inhouseMonthly = monthlyCost + (developerSalary * Math.ceil(teamSize / 2)); // 유지보수 인력
|
1412 |
+
const inhouseTotal = inhouseInitial + (inhouseMonthly * currentPeriod);
|
1413 |
+
|
1414 |
+
// 플랫폼 대여
|
1415 |
+
const platformInitial = 1; // 설정 비용
|
1416 |
+
const platformMonthly = monthlyCost * 2 + 5; // 플랫폼 수수료 포함
|
1417 |
+
const platformTotal = platformInitial + (platformMonthly * currentPeriod);
|
1418 |
+
|
1419 |
+
// 결과 표시
|
1420 |
+
document.getElementById('outsourceInitial').textContent = `${outsourceInitial}K`;
|
1421 |
+
document.getElementById('outsourceMonthly').textContent = `${outsourceMonthly.toFixed(1)}K`;
|
1422 |
+
document.getElementById('outsourceTotal').textContent = `${outsourceTotal.toFixed(1)}K`;
|
1423 |
+
|
1424 |
+
document.getElementById('inhouseInitial').textContent = `${inhouseInitial}K`;
|
1425 |
+
document.getElementById('inhouseMonthly').textContent = `${inhouseMonthly.toFixed(1)}K`;
|
1426 |
+
document.getElementById('inhouseTotal').textContent = `${inhouseTotal.toFixed(1)}K`;
|
1427 |
+
|
1428 |
+
document.getElementById('platformInitial').textContent = `${platformInitial}K`;
|
1429 |
+
document.getElementById('platformMonthly').textContent = `${platformMonthly.toFixed(1)}K`;
|
1430 |
+
document.getElementById('platformTotal').textContent = `${platformTotal.toFixed(1)}K`;
|
1431 |
+
}
|
1432 |
+
|
1433 |
+
// 차트 업데이트
|
1434 |
+
function updateCustomChart(initialCost, monthlyCost) {
|
1435 |
+
const ctx = document.getElementById('customChart').getContext('2d');
|
1436 |
+
|
1437 |
+
if (customChart) {
|
1438 |
+
customChart.destroy();
|
1439 |
+
}
|
1440 |
+
|
1441 |
+
const months = Array.from({length: 12}, (_, i) => `${i + 1}개월`);
|
1442 |
+
const outsourceData = months.map((_, i) => initialCost + (monthlyCost * (i + 1)));
|
1443 |
+
const inhouseData = months.map((_, i) => {
|
1444 |
+
const teamSize = Math.ceil(initialCost / 50);
|
1445 |
+
const inhouseInitial = 10 * teamSize * 3;
|
1446 |
+
const inhouseMonthly = monthlyCost + (10 * Math.ceil(teamSize / 2));
|
1447 |
+
return inhouseInitial + (inhouseMonthly * (i + 1));
|
1448 |
+
});
|
1449 |
+
const platformData = months.map((_, i) => 1 + ((monthlyCost * 2 + 5) * (i + 1)));
|
1450 |
+
|
1451 |
+
customChart = new Chart(ctx, {
|
1452 |
+
type: 'line',
|
1453 |
+
data: {
|
1454 |
+
labels: months,
|
1455 |
+
datasets: [{
|
1456 |
+
label: '외주 용역 개발',
|
1457 |
+
data: outsourceData,
|
1458 |
+
borderColor: '#764ba2',
|
1459 |
+
backgroundColor: 'rgba(118, 75, 162, 0.1)',
|
1460 |
+
tension: 0.4
|
1461 |
+
}, {
|
1462 |
+
label: '자체 인력 개발',
|
1463 |
+
data: inhouseData,
|
1464 |
+
borderColor: '#f5576c',
|
1465 |
+
backgroundColor: 'rgba(245, 87, 108, 0.1)',
|
1466 |
+
tension: 0.4
|
1467 |
+
}, {
|
1468 |
+
label: '플랫폼 대여',
|
1469 |
+
data: platformData,
|
1470 |
+
borderColor: '#4facfe',
|
1471 |
+
backgroundColor: 'rgba(79, 172, 254, 0.1)',
|
1472 |
+
tension: 0.4
|
1473 |
+
}]
|
1474 |
+
},
|
1475 |
+
options: {
|
1476 |
+
responsive: true,
|
1477 |
+
maintainAspectRatio: false,
|
1478 |
+
plugins: {
|
1479 |
+
legend: {
|
1480 |
+
labels: {
|
1481 |
+
color: '#ffffff'
|
1482 |
+
}
|
1483 |
+
},
|
1484 |
+
tooltip: {
|
1485 |
+
callbacks: {
|
1486 |
+
label: function(context) {
|
1487 |
+
return context.dataset.label + ': + context.parsed.y.toFixed(1) + 'K';
|
1488 |
+
}
|
1489 |
+
}
|
1490 |
+
}
|
1491 |
+
},
|
1492 |
+
scales: {
|
1493 |
+
x: {
|
1494 |
+
grid: {
|
1495 |
+
color: 'rgba(255, 255, 255, 0.1)'
|
1496 |
+
},
|
1497 |
+
ticks: {
|
1498 |
+
color: '#a8a8b3'
|
1499 |
+
}
|
1500 |
+
},
|
1501 |
+
y: {
|
1502 |
+
grid: {
|
1503 |
+
color: 'rgba(255, 255, 255, 0.1)'
|
1504 |
+
},
|
1505 |
+
ticks: {
|
1506 |
+
color: '#a8a8b3',
|
1507 |
+
callback: function(value) {
|
1508 |
+
return ' + value + 'K';
|
1509 |
+
}
|
1510 |
+
}
|
1511 |
+
}
|
1512 |
+
}
|
1513 |
+
}
|
1514 |
});
|
1515 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|