Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Spring
SPRING_PROFILES_ACTIVE=local
SERVER_PORT=8081

# Backend API
SAEROK_API_BASE_URL=http://localhost:8080
SAEROK_API_PREFIX=/api/v1

# OAuth
KAKAO_CLIENT_ID=your-kakao-client-id
KAKAO_REDIRECT_URI=http://localhost:8081/auth/kakao/callback
APPLE_CLIENT_ID=your-apple-client-id
APPLE_REDIRECT_URI=http://localhost:8081/auth/apple/callback

# Unsplash
UNSPLASH_ACCESS_KEY=your-unsplash-access-key
UNSPLASH_APP_NAME=your-unsplash-app-name
29 changes: 28 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,32 @@ out/
### VS Code ###
.vscode/

.codex/

.env
context/
.env.local
.env.development
.env.test
.env.production
.env.*.local
!.env.example
context/

### OS ###
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes

### Logs ###
logs/
*.log
*.log.*

### JVM ###
hs_err_pid*
replay_pid*

### Spring Boot ###
/.spring-boot-devtools.properties
16 changes: 16 additions & 0 deletions src/main/java/apu/saerok_admin/infra/stat/StatMetric.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ public enum StatMetric {
false,
Map.of(),
false
),
USER_SIGNUP_SOURCE_TOTAL(
"가입 경로별 누적 사용자",
"가입 경로(소셜 로그인 종류)별 누적 가입자 수",
MetricUnit.COUNT,
true,
Map.of(),
false
),
USER_DEVICE_PLATFORM_TOTAL(
"기기 플랫폼별 누적 사용자",
"기기 플랫폼(iOS/Android/Web 등)별 누적 사용자 수",
MetricUnit.COUNT,
true,
Map.of(),
false
);

private final String label;
Expand Down
49 changes: 48 additions & 1 deletion src/main/resources/static/js/service-insight.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
USER_DAU: 'USER_DAU',
USER_WAU: 'USER_WAU',
USER_MAU: 'USER_MAU',
USER_SIGNUP_SOURCE_TOTAL: 'USER_SIGNUP_SOURCE_TOTAL',
USER_DEVICE_PLATFORM_TOTAL: 'USER_DEVICE_PLATFORM_TOTAL',
};

// (아래부터는 기존 로직 그대로입니다. 색상/그룹/플롯/툴팁/박스플롯 렌더링 등 전체 원본 유지)
Expand Down Expand Up @@ -167,7 +169,8 @@
{ key:'collection', name:'새록', metrics:[METRICS.TOTAL_COUNT, METRICS.PRIVATE_RATIO] },
{ key:'user', name:'유저', metrics:[
METRICS.USER_COMPLETED_TOTAL, METRICS.USER_SIGNUP_DAILY, METRICS.USER_WITHDRAWAL_DAILY,
METRICS.USER_DAU, METRICS.USER_WAU, METRICS.USER_MAU
METRICS.USER_DAU, METRICS.USER_WAU, METRICS.USER_MAU,
METRICS.USER_SIGNUP_SOURCE_TOTAL, METRICS.USER_DEVICE_PLATFORM_TOTAL
] },
{ key:'id', name:'동정 요청', metrics:[METRICS.PENDING_COUNT, METRICS.RESOLVED_COUNT, METRICS.RESOLUTION_STATS] },
];
Expand Down Expand Up @@ -683,6 +686,50 @@
groupSet.add(id);
changed = true;
}
} else if (opt.multiSeries) {
// 일반 다중 시리즈: 컴포넌트별 라인 렌더링
const series = seriesMap.get(metric);
const comps = Array.isArray(series?.components) ? series.components : [];
const labelsMap = (componentLabels[metric] && typeof componentLabels[metric] === 'object') ? componentLabels[metric] : {};
const unit = String(opt.unit || '').toUpperCase();
const yAxis = axisByUnit(unit);

comps.forEach(comp => {
if (!comp || !comp.key) return;
const compKey = comp.key;
const id = datasetIdFor(metric, compKey);
if (p.datasets.has(id)) return;

const compColor = colorForMetric(id);
const compLabel = labelsMap[compKey] || compKey;
const points = (Array.isArray(comp.points) ? comp.points : [])
.map(pt => {
const x = toDateKST(pt.date);
const y = normalizeValue(pt.value, unit);
return (x && y != null) ? { x, y } : null;
})
.filter(Boolean);

const ds = {
_id: id,
label: `${opt.label || metric} (${compLabel})`,
data: points,
parsing: { xAxisKey: 'x', yAxisKey: 'y' },
borderColor: compColor,
backgroundColor: compColor,
tension: 0.25,
pointRadius: 0,
pointHoverRadius: 4,
fill: false,
spanGaps: true,
yAxisID: yAxis,
_saUnit: unit
};
p.chart.data.datasets.push(ds);
p.datasets.add(id);
groupSet.add(id);
changed = true;
});
} else {
const unit = String(optionMap.get(metric)?.unit || '').toUpperCase();
const s = seriesMap.get(metric);
Expand Down