Skip to content
Closed
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 apps/web/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/App.css",
"baseColor": "neutral",
"cssVariables": false
},
"aliases": {
"components": "./src/components",
"utils": "./src/lib/utils",
"ui": "./src/components/ui"
}
}
80 changes: 80 additions & 0 deletions apps/web/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,86 @@
@tailwind components;
@tailwind utilities;

@layer base {
html,
body,
#root {
min-height: 100%;
}

body {
margin: 0;
}

::selection {
background: #0a0a0a;
color: #fff;
}
}

.monochrome-shell {
font-family: 'Avenir Next', 'Century Gothic', sans-serif;
}

.monochrome-shell > div > section,
.monochrome-shell > div > main {
animation: focus-rise 420ms ease-out both;
}

.monochrome-shell > div > section:nth-of-type(2) {
animation-delay: 80ms;
}

.monochrome-shell > div > main {
animation-delay: 140ms;
}

.monochrome-shell header .dropdown > .btn,
.monochrome-shell header .btn-circle {
min-height: 2.25rem;
height: 2.25rem;
width: 2.25rem;
border-radius: 0;
background: transparent;
color: #0a0a0a;
}

.monochrome-shell header .dropdown-content {
border: 1px solid #171717;
border-radius: 0;
background: #fff;
color: #171717;
}

.monochrome-log-editor {
background-image: repeating-linear-gradient(
to bottom,
transparent 0,
transparent 27px,
rgb(0 0 0 / 7%) 27px,
rgb(0 0 0 / 7%) 28px
);
background-position-y: 15px;
}

@keyframes focus-rise {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@media (prefers-reduced-motion: reduce) {
.monochrome-shell > div > section,
.monochrome-shell > div > main {
animation: none;
}
}

@keyframes shake {
0% {
transform: translateX(0);
Expand Down
57 changes: 45 additions & 12 deletions apps/web/src/components/charts/AvailableRestTimeChart/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Area,
AreaChart,
CartesianGrid,
ReferenceLine,
ResponsiveContainer,
Tooltip,
XAxis,
Expand Down Expand Up @@ -31,46 +32,78 @@ export const AvailableRestTimeChart = ({

const gradientId = 'availableRestTimeSplitColor';

if (data.length < 2) {
return (
<div className="flex h-full min-h-44 items-center justify-center border border-dashed border-neutral-300 font-mono text-[10px] uppercase tracking-[0.14em] text-neutral-400">
기록을 두 개 이상 추가하면 시간 잔고가 표시됩니다
</div>
);
}

return (
<ResponsiveContainer className="min-h-0">
<ResponsiveContainer className="min-h-0" width="100%" height="100%">
<AreaChart
data={data}
margin={{
top: 30,
right: 30,
left: 0,
bottom: 30,
top: 24,
right: 16,
left: -12,
bottom: 8,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<CartesianGrid
stroke="#deded9"
strokeDasharray="2 5"
vertical={false}
/>
<XAxis
dataKey="offset"
type="number"
tick={{ fontSize: 16 }}
axisLine={false}
tickLine={false}
tick={{ fontSize: 10, fill: '#737373' }}
tickFormatter={minutesToTimeString}
domain={[minXAxisDomain, maxXAxisDomain]}
/>
<YAxis
tick={{ fontSize: 16 }}
axisLine={false}
tickLine={false}
tick={{ fontSize: 10, fill: '#737373' }}
domain={yAxisConfig.domain}
ticks={yAxisConfig.ticks}
tickFormatter={(value) => `${value}`}
/>
<Tooltip
labelFormatter={minutesToTimeString}
formatter={(value: number | string) => [`${value}분`, '초과 휴식']}
formatter={(value: number | string) => [`${value}분`, '소비 − 생산']}
contentStyle={{
border: '1px solid #171717',
borderRadius: 0,
boxShadow: 'none',
fontSize: 11,
}}
/>
<ReferenceLine y={0} stroke="#171717" strokeDasharray="3 3" />
<defs>
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
<stop offset={gradientOffset} stopColor="red" stopOpacity={1} />
<stop offset={gradientOffset} stopColor="green" stopOpacity={1} />
<stop
offset={gradientOffset}
stopColor="#a3a3a3"
stopOpacity={0.55}
/>
<stop
offset={gradientOffset}
stopColor="#171717"
stopOpacity={0.18}
/>
</linearGradient>
</defs>
<Area
type="monotone"
dataKey="need"
unit="min"
stroke="#000"
stroke="#171717"
strokeWidth={1.5}
fill={`url(#${gradientId})`}
/>
{getPoints(data)}
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/components/charts/AvailableRestTimeChart/points.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const getPoints = (data: ChartDataPoint[]) => {
const points: ReactElement[] = [];

if (highPoint) {
const color = highPoint.need >= 0 ? 'red' : 'green';
const color = highPoint.need >= 0 ? '#171717' : '#737373';
points.push(
<ReferenceDot
key="high"
Expand All @@ -34,7 +34,7 @@ export const getPoints = (data: ChartDataPoint[]) => {
}

if (lowPoint) {
const color = lowPoint.need >= 0 ? 'red' : 'green';
const color = lowPoint.need >= 0 ? '#171717' : '#737373';
points.push(
<ReferenceDot
key="low"
Expand Down Expand Up @@ -117,7 +117,7 @@ function isNearPoint(
type CurrentPointConfig = {
point: ChartDataPoint | null;
shouldShow: boolean;
color: 'red' | 'green';
color: string;
position: 'top' | 'bottom';
};

Expand All @@ -131,14 +131,14 @@ function getCurrentPointConfig(
return {
point: null,
shouldShow: false,
color: 'red',
color: '#171717',
position: 'top',
};
}

const shouldShow =
!isNearPoint(currPoint, highPoint) && !isNearPoint(currPoint, lowPoint);
const color = currPoint.need >= 0 ? 'red' : 'green';
const color = currPoint.need >= 0 ? '#171717' : '#737373';
const position = currPoint.need >= 0 ? 'top' : 'bottom';

return { point: currPoint, shouldShow, color, position };
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/charts/DayRatioBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const DayRatioBar = ({ logs }: { logs: Log[] }) => {

if (isEmpty || totalDuration <= 0) {
return (
<div className="flex h-3 w-full min-w-0 overflow-hidden rounded-full bg-gray-200" />
<div className="flex h-2 w-full min-w-0 overflow-hidden bg-neutral-200" />
);
}

Expand Down Expand Up @@ -36,14 +36,14 @@ export const DayRatioBar = ({ logs }: { logs: Log[] }) => {

return (
<div
className="isolate flex h-3 w-full min-w-0 transform-gpu flex-nowrap overflow-hidden rounded-full bg-gray-100"
className="isolate flex h-2 w-full min-w-0 transform-gpu flex-nowrap overflow-hidden bg-neutral-100"
style={{ WebkitMaskImage: '-webkit-radial-gradient(white, black)' }}
>
{blocks.map((block, idx) => (
<div
key={idx}
className={`transition-all duration-500 ease-in-out ${
block.type === 'productive' ? 'bg-green-500' : 'bg-red-500'
block.type === 'productive' ? 'bg-neutral-900' : 'bg-neutral-300'
}`}
style={{ width: `${(block.duration / totalDuration) * 100}%` }}
title={`${block.type === 'productive' ? '생산' : '소비'}: ${block.duration}분`}
Expand Down
66 changes: 43 additions & 23 deletions apps/web/src/components/charts/ProductivePaceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,66 +14,86 @@ import { Log } from '../../utils/PaceUtil';

interface ProductivePaceChartProps {
data: Log[];
totalAvg: number;
todayAvg: number;
targetPace: number;
}

export const ProductivePaceChart = ({
data,
totalAvg,
todayAvg,
targetPace,
}: ProductivePaceChartProps) => {
if (data.length < 2) {
return (
<div className="flex h-full min-h-44 items-center justify-center border border-dashed border-neutral-300 font-mono text-[10px] uppercase tracking-[0.14em] text-neutral-400">
기록을 두 개 이상 추가하면 페이스가 표시됩니다
</div>
);
}

return (
<ResponsiveContainer className="min-h-0">
<ResponsiveContainer className="min-h-0" width="100%" height="100%">
<AreaChart
data={data}
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
top: 18,
right: 18,
left: -12,
bottom: 8,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<CartesianGrid
stroke="#deded9"
strokeDasharray="2 5"
vertical={false}
/>
<XAxis
dataKey="offset"
type="number"
tick={{ fontSize: 16 }}
axisLine={false}
tickLine={false}
tick={{ fontSize: 10, fill: '#737373' }}
tickFormatter={minutesToTimeString}
domain={[8 * 60, 27 * 60]}
/>
<YAxis
domain={[0, 60]}
allowDataOverflow={true}
tick={{ fontSize: 16 }}
axisLine={false}
tickLine={false}
tick={{ fontSize: 10, fill: '#737373' }}
/>
<Tooltip labelFormatter={minutesToTimeString} />
<ReferenceLine
y={targetPace}
stroke="red"
label={{ value: `목표: ${targetPace}min/h`, fontSize: 16 }}
<Tooltip
labelFormatter={minutesToTimeString}
contentStyle={{
border: '1px solid #171717',
borderRadius: 0,
boxShadow: 'none',
fontSize: 11,
}}
/>
<ReferenceLine
y={totalAvg}
stroke="blue"
y={targetPace}
stroke="#171717"
strokeDasharray="4 4"
label={{
value: `[미지원] 전체 평균(${totalAvg}min/h)`,
fontSize: 16,
value: `목표 ${targetPace}`,
fontSize: 10,
fill: '#171717',
}}
/>
<ReferenceLine
y={todayAvg}
stroke="green"
label={{ value: `오늘 평균(${todayAvg}min/h)`, fontSize: 16 }}
stroke="#737373"
label={{ value: `오늘 ${todayAvg}`, fontSize: 10, fill: '#737373' }}
/>
<Area
type="monotone"
dataKey={(o) => o.pace}
unit="min/h"
stroke="darkgreen"
fill="green"
stroke="#171717"
strokeWidth={1.5}
fill="#d4d4d4"
/>
</AreaChart>
</ResponsiveContainer>
Expand Down
Loading
Loading