refactor: update production controller to use AuthenticatedRequest
- Modified the production controller to replace the generic Request type with AuthenticatedRequest for better type safety and to ensure user authentication is handled correctly. - This change enhances the security and clarity of the API endpoints related to production plan management, ensuring that user-specific data is accessed appropriately. Made-with: Cursor
This commit is contained in:
parent
ba39ebf341
commit
79e3420ff3
|
|
@ -2,13 +2,14 @@
|
||||||
* 생산계획 컨트롤러
|
* 생산계획 컨트롤러
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response } from "express";
|
import { Response } from "express";
|
||||||
|
import { AuthenticatedRequest } from "../types/auth";
|
||||||
import * as productionService from "../services/productionPlanService";
|
import * as productionService from "../services/productionPlanService";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
// ─── 수주 데이터 조회 (품목별 그룹핑) ───
|
// ─── 수주 데이터 조회 (품목별 그룹핑) ───
|
||||||
|
|
||||||
export async function getOrderSummary(req: Request, res: Response) {
|
export async function getOrderSummary(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const { excludePlanned, itemCode, itemName } = req.query;
|
const { excludePlanned, itemCode, itemName } = req.query;
|
||||||
|
|
@ -28,7 +29,7 @@ export async function getOrderSummary(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 안전재고 부족분 조회 ───
|
// ─── 안전재고 부족분 조회 ───
|
||||||
|
|
||||||
export async function getStockShortage(req: Request, res: Response) {
|
export async function getStockShortage(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const data = await productionService.getStockShortage(companyCode);
|
const data = await productionService.getStockShortage(companyCode);
|
||||||
|
|
@ -41,7 +42,7 @@ export async function getStockShortage(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 생산계획 상세 조회 ───
|
// ─── 생산계획 상세 조회 ───
|
||||||
|
|
||||||
export async function getPlanById(req: Request, res: Response) {
|
export async function getPlanById(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const planId = parseInt(req.params.id, 10);
|
const planId = parseInt(req.params.id, 10);
|
||||||
|
|
@ -59,7 +60,7 @@ export async function getPlanById(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 생산계획 수정 ───
|
// ─── 생산계획 수정 ───
|
||||||
|
|
||||||
export async function updatePlan(req: Request, res: Response) {
|
export async function updatePlan(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const planId = parseInt(req.params.id, 10);
|
const planId = parseInt(req.params.id, 10);
|
||||||
|
|
@ -78,7 +79,7 @@ export async function updatePlan(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 생산계획 삭제 ───
|
// ─── 생산계획 삭제 ───
|
||||||
|
|
||||||
export async function deletePlan(req: Request, res: Response) {
|
export async function deletePlan(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const planId = parseInt(req.params.id, 10);
|
const planId = parseInt(req.params.id, 10);
|
||||||
|
|
@ -96,7 +97,7 @@ export async function deletePlan(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 자동 스케줄 생성 ───
|
// ─── 자동 스케줄 생성 ───
|
||||||
|
|
||||||
export async function generateSchedule(req: Request, res: Response) {
|
export async function generateSchedule(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const createdBy = req.user!.userId;
|
const createdBy = req.user!.userId;
|
||||||
|
|
@ -116,7 +117,7 @@ export async function generateSchedule(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 스케줄 병합 ───
|
// ─── 스케줄 병합 ───
|
||||||
|
|
||||||
export async function mergeSchedules(req: Request, res: Response) {
|
export async function mergeSchedules(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const mergedBy = req.user!.userId;
|
const mergedBy = req.user!.userId;
|
||||||
|
|
@ -142,7 +143,7 @@ export async function mergeSchedules(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 반제품 계획 자동 생성 ───
|
// ─── 반제품 계획 자동 생성 ───
|
||||||
|
|
||||||
export async function generateSemiSchedule(req: Request, res: Response) {
|
export async function generateSemiSchedule(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const createdBy = req.user!.userId;
|
const createdBy = req.user!.userId;
|
||||||
|
|
@ -167,7 +168,7 @@ export async function generateSemiSchedule(req: Request, res: Response) {
|
||||||
|
|
||||||
// ─── 스케줄 분할 ───
|
// ─── 스케줄 분할 ───
|
||||||
|
|
||||||
export async function splitSchedule(req: Request, res: Response) {
|
export async function splitSchedule(req: AuthenticatedRequest, res: Response) {
|
||||||
try {
|
try {
|
||||||
const companyCode = req.user!.companyCode;
|
const companyCode = req.user!.companyCode;
|
||||||
const splitBy = req.user!.userId;
|
const splitBy = req.user!.userId;
|
||||||
|
|
|
||||||
|
|
@ -226,11 +226,11 @@ export function TimelineSchedulerComponent({
|
||||||
// 디자인 모드 플레이스홀더
|
// 디자인 모드 플레이스홀더
|
||||||
if (isDesignMode) {
|
if (isDesignMode) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full min-h-[200px] border-2 border-dashed border-muted-foreground/30 rounded-lg flex items-center justify-center bg-muted/10">
|
<div className="flex h-full min-h-[200px] w-full items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/30 bg-muted/10">
|
||||||
<div className="text-center text-muted-foreground">
|
<div className="text-center text-muted-foreground">
|
||||||
<Calendar className="h-8 w-8 mx-auto mb-2" />
|
<Calendar className="mx-auto mb-2 h-6 w-6 sm:h-8 sm:w-8" />
|
||||||
<p className="text-sm font-medium">타임라인 스케줄러</p>
|
<p className="text-xs font-medium sm:text-sm">타임라인 스케줄러</p>
|
||||||
<p className="text-xs mt-1">
|
<p className="mt-1 text-[10px] sm:text-xs">
|
||||||
{config.selectedTable
|
{config.selectedTable
|
||||||
? `테이블: ${config.selectedTable}`
|
? `테이블: ${config.selectedTable}`
|
||||||
: "테이블을 선택하세요"}
|
: "테이블을 선택하세요"}
|
||||||
|
|
@ -244,12 +244,12 @@ export function TimelineSchedulerComponent({
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full flex items-center justify-center bg-muted/10 rounded-lg"
|
className="flex w-full items-center justify-center rounded-lg bg-muted/10"
|
||||||
style={{ height: config.height || 500 }}
|
style={{ height: config.height || 500 }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 text-muted-foreground">
|
<div className="flex items-center gap-2 text-muted-foreground">
|
||||||
<Loader2 className="h-5 w-5 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin sm:h-5 sm:w-5" />
|
||||||
<span className="text-sm">로딩 중...</span>
|
<span className="text-xs sm:text-sm">로딩 중...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -259,12 +259,12 @@ export function TimelineSchedulerComponent({
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full flex items-center justify-center bg-destructive/10 rounded-lg"
|
className="flex w-full items-center justify-center rounded-lg bg-destructive/10"
|
||||||
style={{ height: config.height || 500 }}
|
style={{ height: config.height || 500 }}
|
||||||
>
|
>
|
||||||
<div className="text-center text-destructive">
|
<div className="text-center text-destructive">
|
||||||
<p className="text-sm font-medium">오류 발생</p>
|
<p className="text-xs font-medium sm:text-sm">오류 발생</p>
|
||||||
<p className="text-xs mt-1">{error}</p>
|
<p className="mt-1 text-[10px] sm:text-xs">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -274,13 +274,13 @@ export function TimelineSchedulerComponent({
|
||||||
if (schedules.length === 0) {
|
if (schedules.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full flex items-center justify-center bg-muted/10 rounded-lg border"
|
className="flex w-full items-center justify-center rounded-lg border bg-muted/10"
|
||||||
style={{ height: config.height || 500 }}
|
style={{ height: config.height || 500 }}
|
||||||
>
|
>
|
||||||
<div className="text-center text-muted-foreground">
|
<div className="text-center text-muted-foreground">
|
||||||
<Calendar className="h-10 w-10 mx-auto mb-3 opacity-50" />
|
<Calendar className="mx-auto mb-2 h-8 w-8 opacity-50 sm:mb-3 sm:h-10 sm:w-10" />
|
||||||
<p className="text-sm font-medium">스케줄 데이터가 없습니다</p>
|
<p className="text-xs font-medium sm:text-sm">스케줄 데이터가 없습니다</p>
|
||||||
<p className="text-xs mt-2 max-w-[200px]">
|
<p className="mt-1.5 max-w-[200px] text-[10px] sm:mt-2 sm:text-xs">
|
||||||
좌측 테이블에서 품목을 선택하거나,<br />
|
좌측 테이블에서 품목을 선택하거나,<br />
|
||||||
스케줄 생성 버튼을 눌러 스케줄을 생성하세요
|
스케줄 생성 버튼을 눌러 스케줄을 생성하세요
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -292,7 +292,7 @@ export function TimelineSchedulerComponent({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="w-full border rounded-lg overflow-hidden bg-background"
|
className="w-full overflow-hidden rounded-lg border bg-background"
|
||||||
style={{
|
style={{
|
||||||
height: config.height || 500,
|
height: config.height || 500,
|
||||||
maxHeight: config.maxHeight,
|
maxHeight: config.maxHeight,
|
||||||
|
|
@ -300,24 +300,24 @@ export function TimelineSchedulerComponent({
|
||||||
>
|
>
|
||||||
{/* 툴바 */}
|
{/* 툴바 */}
|
||||||
{config.showToolbar !== false && (
|
{config.showToolbar !== false && (
|
||||||
<div className="flex items-center justify-between px-3 py-2 border-b bg-muted/30">
|
<div className="flex items-center justify-between border-b bg-muted/30 px-2 py-1.5 sm:px-3 sm:py-2">
|
||||||
{/* 네비게이션 */}
|
{/* 네비게이션 */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-0.5 sm:gap-1">
|
||||||
{config.showNavigation !== false && (
|
{config.showNavigation !== false && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={goToPrevious}
|
onClick={goToPrevious}
|
||||||
className="h-7 px-2"
|
className="h-6 px-1.5 sm:h-7 sm:px-2"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={goToToday}
|
onClick={goToToday}
|
||||||
className="h-7 px-2"
|
className="h-6 px-1.5 text-xs sm:h-7 sm:px-2 sm:text-sm"
|
||||||
>
|
>
|
||||||
오늘
|
오늘
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -325,15 +325,15 @@ export function TimelineSchedulerComponent({
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={goToNext}
|
onClick={goToNext}
|
||||||
className="h-7 px-2"
|
className="h-6 px-1.5 sm:h-7 sm:px-2"
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 현재 날짜 범위 표시 */}
|
{/* 현재 날짜 범위 표시 */}
|
||||||
<span className="ml-2 text-sm text-muted-foreground">
|
<span className="ml-1 text-[10px] text-muted-foreground sm:ml-2 sm:text-sm">
|
||||||
{viewStartDate.getFullYear()}년 {viewStartDate.getMonth() + 1}월{" "}
|
{viewStartDate.getFullYear()}년 {viewStartDate.getMonth() + 1}월{" "}
|
||||||
{viewStartDate.getDate()}일 ~{" "}
|
{viewStartDate.getDate()}일 ~{" "}
|
||||||
{viewEndDate.getMonth() + 1}월 {viewEndDate.getDate()}일
|
{viewEndDate.getMonth() + 1}월 {viewEndDate.getDate()}일
|
||||||
|
|
@ -341,20 +341,20 @@ export function TimelineSchedulerComponent({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 오른쪽 컨트롤 */}
|
{/* 오른쪽 컨트롤 */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1 sm:gap-2">
|
||||||
{/* 줌 컨트롤 */}
|
{/* 줌 컨트롤 */}
|
||||||
{config.showZoomControls !== false && (
|
{config.showZoomControls !== false && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-0.5 sm:gap-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleZoomOut}
|
onClick={handleZoomOut}
|
||||||
disabled={zoomLevel === "month"}
|
disabled={zoomLevel === "month"}
|
||||||
className="h-7 px-2"
|
className="h-6 px-1.5 sm:h-7 sm:px-2"
|
||||||
>
|
>
|
||||||
<ZoomOut className="h-4 w-4" />
|
<ZoomOut className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text-xs text-muted-foreground min-w-[24px] text-center">
|
<span className="min-w-[20px] text-center text-[10px] text-muted-foreground sm:min-w-[24px] sm:text-xs">
|
||||||
{zoomLevelOptions.find((o) => o.value === zoomLevel)?.label}
|
{zoomLevelOptions.find((o) => o.value === zoomLevel)?.label}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -362,9 +362,9 @@ export function TimelineSchedulerComponent({
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleZoomIn}
|
onClick={handleZoomIn}
|
||||||
disabled={zoomLevel === "day"}
|
disabled={zoomLevel === "day"}
|
||||||
className="h-7 px-2"
|
className="h-6 px-1.5 sm:h-7 sm:px-2"
|
||||||
>
|
>
|
||||||
<ZoomIn className="h-4 w-4" />
|
<ZoomIn className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -375,9 +375,9 @@ export function TimelineSchedulerComponent({
|
||||||
variant="default"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleAddClick}
|
onClick={handleAddClick}
|
||||||
className="h-7"
|
className="h-6 text-xs sm:h-7 sm:text-sm"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
<Plus className="mr-0.5 h-3.5 w-3.5 sm:mr-1 sm:h-4 sm:w-4" />
|
||||||
추가
|
추가
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -138,13 +138,13 @@ export function ResourceRow({
|
||||||
>
|
>
|
||||||
{/* 리소스 컬럼 */}
|
{/* 리소스 컬럼 */}
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 border-r bg-muted/30 flex items-center px-3 sticky left-0 z-10"
|
className="sticky left-0 z-10 flex shrink-0 items-center border-r bg-muted/30 px-2 sm:px-3"
|
||||||
style={{ width: resourceColumnWidth }}
|
style={{ width: resourceColumnWidth }}
|
||||||
>
|
>
|
||||||
<div className="truncate">
|
<div className="truncate">
|
||||||
<div className="font-medium text-sm truncate">{resource.name}</div>
|
<div className="truncate text-[10px] font-medium sm:text-sm">{resource.name}</div>
|
||||||
{resource.group && (
|
{resource.group && (
|
||||||
<div className="text-xs text-muted-foreground truncate">
|
<div className="truncate text-[9px] text-muted-foreground sm:text-xs">
|
||||||
{resource.group}
|
{resource.group}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -170,7 +170,7 @@ export function ResourceRow({
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-r h-full",
|
"h-full border-r",
|
||||||
isWeekend && "bg-muted/20",
|
isWeekend && "bg-muted/20",
|
||||||
isToday && "bg-primary/5",
|
isToday && "bg-primary/5",
|
||||||
isMonthStart && "border-l-2 border-l-primary/20"
|
isMonthStart && "border-l-2 border-l-primary/20"
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,9 @@ export function ScheduleBar({
|
||||||
<div
|
<div
|
||||||
ref={barRef}
|
ref={barRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute rounded-md shadow-sm cursor-pointer transition-shadow",
|
"absolute cursor-pointer rounded-md shadow-sm transition-shadow",
|
||||||
"hover:shadow-md hover:z-10",
|
"hover:z-10 hover:shadow-md",
|
||||||
isDragging && "opacity-70 shadow-lg z-20",
|
isDragging && "z-20 opacity-70 shadow-lg",
|
||||||
isResizing && "z-20",
|
isResizing && "z-20",
|
||||||
draggable && "cursor-grab",
|
draggable && "cursor-grab",
|
||||||
isDragging && "cursor-grabbing"
|
isDragging && "cursor-grabbing"
|
||||||
|
|
@ -145,19 +145,19 @@ export function ScheduleBar({
|
||||||
{/* 진행률 바 */}
|
{/* 진행률 바 */}
|
||||||
{config.showProgress && schedule.progress !== undefined && (
|
{config.showProgress && schedule.progress !== undefined && (
|
||||||
<div
|
<div
|
||||||
className="absolute inset-y-0 left-0 rounded-l-md opacity-30 bg-white"
|
className="absolute inset-y-0 left-0 rounded-l-md bg-white opacity-30"
|
||||||
style={{ width: progressWidth }}
|
style={{ width: progressWidth }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 제목 */}
|
{/* 제목 */}
|
||||||
<div className="relative z-10 px-2 py-1 text-xs text-white truncate font-medium">
|
<div className="relative z-10 truncate px-1.5 py-0.5 text-[10px] font-medium text-white sm:px-2 sm:py-1 sm:text-xs">
|
||||||
{schedule.title}
|
{schedule.title}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 진행률 텍스트 */}
|
{/* 진행률 텍스트 */}
|
||||||
{config.showProgress && schedule.progress !== undefined && (
|
{config.showProgress && schedule.progress !== undefined && (
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-white/80 font-medium">
|
<div className="absolute right-1 top-1/2 -translate-y-1/2 text-[8px] font-medium text-white/80 sm:right-2 sm:text-[10px]">
|
||||||
{schedule.progress}%
|
{schedule.progress}%
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -165,7 +165,7 @@ export function ScheduleBar({
|
||||||
{/* 리사이즈 핸들 - 왼쪽 */}
|
{/* 리사이즈 핸들 - 왼쪽 */}
|
||||||
{resizable && (
|
{resizable && (
|
||||||
<div
|
<div
|
||||||
className="absolute left-0 top-0 bottom-0 w-2 cursor-ew-resize hover:bg-white/20 rounded-l-md"
|
className="absolute bottom-0 left-0 top-0 w-1.5 cursor-ew-resize rounded-l-md hover:bg-white/20 sm:w-2"
|
||||||
onMouseDown={(e) => handleResizeStart("start", e)}
|
onMouseDown={(e) => handleResizeStart("start", e)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -173,7 +173,7 @@ export function ScheduleBar({
|
||||||
{/* 리사이즈 핸들 - 오른쪽 */}
|
{/* 리사이즈 핸들 - 오른쪽 */}
|
||||||
{resizable && (
|
{resizable && (
|
||||||
<div
|
<div
|
||||||
className="absolute right-0 top-0 bottom-0 w-2 cursor-ew-resize hover:bg-white/20 rounded-r-md"
|
className="absolute bottom-0 right-0 top-0 w-1.5 cursor-ew-resize rounded-r-md hover:bg-white/20 sm:w-2"
|
||||||
onMouseDown={(e) => handleResizeStart("end", e)}
|
onMouseDown={(e) => handleResizeStart("end", e)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ export function TimelineHeader({
|
||||||
<div className="flex" style={{ height: headerHeight / 2 }}>
|
<div className="flex" style={{ height: headerHeight / 2 }}>
|
||||||
{/* 리소스 컬럼 헤더 */}
|
{/* 리소스 컬럼 헤더 */}
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 border-r bg-muted/50 flex items-center justify-center font-medium text-sm"
|
className="flex shrink-0 items-center justify-center border-r bg-muted/50 text-[10px] font-medium sm:text-sm"
|
||||||
style={{ width: resourceColumnWidth }}
|
style={{ width: resourceColumnWidth }}
|
||||||
>
|
>
|
||||||
리소스
|
리소스
|
||||||
|
|
@ -150,7 +150,7 @@ export function TimelineHeader({
|
||||||
{monthGroups.map((group, idx) => (
|
{monthGroups.map((group, idx) => (
|
||||||
<div
|
<div
|
||||||
key={`${group.year}-${group.month}-${idx}`}
|
key={`${group.year}-${group.month}-${idx}`}
|
||||||
className="border-r flex items-center justify-center text-xs font-medium text-muted-foreground"
|
className="flex items-center justify-center border-r text-[10px] font-medium text-muted-foreground sm:text-xs"
|
||||||
style={{ width: group.count * cellWidth }}
|
style={{ width: group.count * cellWidth }}
|
||||||
>
|
>
|
||||||
{group.year}년 {group.month}
|
{group.year}년 {group.month}
|
||||||
|
|
@ -162,7 +162,7 @@ export function TimelineHeader({
|
||||||
<div className="flex" style={{ height: headerHeight / 2 }}>
|
<div className="flex" style={{ height: headerHeight / 2 }}>
|
||||||
{/* 리소스 컬럼 (빈칸) */}
|
{/* 리소스 컬럼 (빈칸) */}
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 border-r bg-muted/50"
|
className="shrink-0 border-r bg-muted/50"
|
||||||
style={{ width: resourceColumnWidth }}
|
style={{ width: resourceColumnWidth }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ export function TimelineHeader({
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-r flex items-center justify-center text-xs",
|
"flex items-center justify-center border-r text-[10px] sm:text-xs",
|
||||||
cell.isToday && "bg-primary/10 font-bold text-primary",
|
cell.isToday && "bg-primary/10 font-bold text-primary",
|
||||||
cell.isWeekend && !cell.isToday && "bg-muted/30 text-muted-foreground",
|
cell.isWeekend && !cell.isToday && "bg-muted/30 text-muted-foreground",
|
||||||
cell.isMonthStart && "border-l-2 border-l-primary/30"
|
cell.isMonthStart && "border-l-2 border-l-primary/30"
|
||||||
|
|
@ -186,7 +186,7 @@ export function TimelineHeader({
|
||||||
{/* 오늘 표시선 */}
|
{/* 오늘 표시선 */}
|
||||||
{showTodayLine && todayPosition !== null && (
|
{showTodayLine && todayPosition !== null && (
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 bottom-0 w-0.5 bg-primary z-30 pointer-events-none"
|
className="pointer-events-none absolute bottom-0 top-0 z-30 w-0.5 bg-primary"
|
||||||
style={{ left: todayPosition }}
|
style={{ left: todayPosition }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue