feat: 순서변경 ▲/▼ 버튼 추가 (터치 지원) + 단가표 테이블 스크롤 수정
- ReorderButtons 공통 컴포넌트 신규 생성 (molecules) - 패턴B(리스트): RankManagement, TitleManagement, CategoryManagement - 패턴A(테이블): ProcessDetail, ProcessForm, ChecklistDetail - 패턴C(컴포넌트): DraggableSection, DraggableField, HierarchyTab - 모바일: GripVertical 숨김, ▲/▼ 버튼만 표시 - 데스크톱: GripVertical + ▲/▼ 버튼 모두 표시 - 단가표 단가정보 테이블 overflow-hidden → overflow-x-auto + min-w 적용
This commit is contained in:
@@ -387,14 +387,14 @@ export function PermissionManagement() {
|
||||
}) => (
|
||||
<div className="flex items-center gap-2 flex-wrap ml-auto">
|
||||
{selItems.size > 0 && (
|
||||
<Button variant="destructive" onClick={handleBulkDelete}>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
선택 삭제 ({selItems.size})
|
||||
<Button variant="destructive" size="sm" className="md:size-default" onClick={handleBulkDelete}>
|
||||
<Trash2 className="h-4 w-4 md:mr-2" />
|
||||
<span className="hidden md:inline">선택 삭제 ({selItems.size})</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleAdd}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
역할 등록
|
||||
<Button size="sm" className="md:size-default" onClick={handleAdd}>
|
||||
<Plus className="h-4 w-4 md:mr-2" />
|
||||
<span className="hidden md:inline">역할 등록</span>
|
||||
</Button>
|
||||
</div>
|
||||
), [handleBulkDelete, handleAdd]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { Award, Plus, GripVertical, Pencil, Trash2, Loader2 } from 'lucide-react';
|
||||
import { ReorderButtons } from '@/components/molecules';
|
||||
import { ContentSkeleton } from '@/components/ui/skeleton';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -185,6 +186,33 @@ export function RankManagement() {
|
||||
setDraggedItem(index);
|
||||
};
|
||||
|
||||
// 화살표 버튼으로 순서 변경
|
||||
const handleMoveItem = async (fromIndex: number, toIndex: number) => {
|
||||
const newRanks = [...ranks];
|
||||
const [moved] = newRanks.splice(fromIndex, 1);
|
||||
newRanks.splice(toIndex, 0, moved);
|
||||
const reordered = newRanks.map((rank, idx) => ({ ...rank, order: idx + 1 }));
|
||||
setRanks(reordered);
|
||||
|
||||
try {
|
||||
const items = reordered.map((rank, idx) => ({
|
||||
id: rank.id,
|
||||
sort_order: idx + 1,
|
||||
}));
|
||||
const result = await reorderRanks(items);
|
||||
if (result.success) {
|
||||
toast.success('순서가 변경되었습니다.');
|
||||
} else {
|
||||
toast.error(result.error || '순서 변경에 실패했습니다.');
|
||||
loadRanks();
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
toast.error('순서 변경에 실패했습니다.');
|
||||
loadRanks();
|
||||
}
|
||||
};
|
||||
|
||||
// 키보드로 추가 (한글 IME 조합 중에는 무시)
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
|
||||
@@ -196,7 +224,7 @@ export function RankManagement() {
|
||||
<PageLayout>
|
||||
<PageHeader
|
||||
title="직급관리"
|
||||
description="사원의 직급을 관리합니다. 드래그하여 순서를 변경할 수 있습니다."
|
||||
description="사원의 직급을 관리합니다. 화살표 버튼 또는 드래그하여 순서를 변경할 수 있습니다."
|
||||
icon={Award}
|
||||
/>
|
||||
|
||||
@@ -246,8 +274,18 @@ export function RankManagement() {
|
||||
draggedItem === index ? 'opacity-50 bg-muted' : ''
|
||||
}`}
|
||||
>
|
||||
{/* 드래그 핸들 */}
|
||||
<GripVertical className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
||||
{/* 드래그 핸들 (PC만) */}
|
||||
<GripVertical className="h-5 w-5 text-muted-foreground flex-shrink-0 hidden md:block" />
|
||||
|
||||
{/* 순서 변경 버튼 */}
|
||||
<ReorderButtons
|
||||
onMoveUp={() => handleMoveItem(index, index - 1)}
|
||||
onMoveDown={() => handleMoveItem(index, index + 1)}
|
||||
isFirst={index === 0}
|
||||
isLast={index === ranks.length - 1}
|
||||
disabled={isSubmitting}
|
||||
size="xs"
|
||||
/>
|
||||
|
||||
{/* 순서 번호 */}
|
||||
<span className="text-sm text-muted-foreground w-8">
|
||||
@@ -295,7 +333,7 @@ export function RankManagement() {
|
||||
|
||||
{/* 안내 문구 */}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
※ 직급 순서는 드래그 앤 드롭으로 변경할 수 있습니다.
|
||||
※ 화살표 버튼으로 순서를 변경할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { Briefcase, Plus, GripVertical, Pencil, Trash2, Loader2 } from 'lucide-react';
|
||||
import { ReorderButtons } from '@/components/molecules';
|
||||
import { ContentSkeleton } from '@/components/ui/skeleton';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -185,6 +186,33 @@ export function TitleManagement() {
|
||||
setDraggedItem(index);
|
||||
};
|
||||
|
||||
// 화살표 버튼으로 순서 변경
|
||||
const handleMoveItem = async (fromIndex: number, toIndex: number) => {
|
||||
const newTitles = [...titles];
|
||||
const [moved] = newTitles.splice(fromIndex, 1);
|
||||
newTitles.splice(toIndex, 0, moved);
|
||||
const reordered = newTitles.map((title, idx) => ({ ...title, order: idx + 1 }));
|
||||
setTitles(reordered);
|
||||
|
||||
try {
|
||||
const items = reordered.map((title, idx) => ({
|
||||
id: title.id,
|
||||
sort_order: idx + 1,
|
||||
}));
|
||||
const result = await reorderTitles(items);
|
||||
if (result.success) {
|
||||
toast.success('순서가 변경되었습니다.');
|
||||
} else {
|
||||
toast.error(result.error || '순서 변경에 실패했습니다.');
|
||||
loadTitles();
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
toast.error('순서 변경에 실패했습니다.');
|
||||
loadTitles();
|
||||
}
|
||||
};
|
||||
|
||||
// 키보드로 추가 (한글 IME 조합 중에는 무시)
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
|
||||
@@ -196,7 +224,7 @@ export function TitleManagement() {
|
||||
<PageLayout>
|
||||
<PageHeader
|
||||
title="직책관리"
|
||||
description="사원의 직책을 관리합니다. 드래그하여 순서를 변경할 수 있습니다."
|
||||
description="사원의 직책을 관리합니다. 화살표 버튼 또는 드래그하여 순서를 변경할 수 있습니다."
|
||||
icon={Briefcase}
|
||||
/>
|
||||
|
||||
@@ -246,8 +274,18 @@ export function TitleManagement() {
|
||||
draggedItem === index ? 'opacity-50 bg-muted' : ''
|
||||
}`}
|
||||
>
|
||||
{/* 드래그 핸들 */}
|
||||
<GripVertical className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
||||
{/* 드래그 핸들 (PC만) */}
|
||||
<GripVertical className="h-5 w-5 text-muted-foreground flex-shrink-0 hidden md:block" />
|
||||
|
||||
{/* 순서 변경 버튼 */}
|
||||
<ReorderButtons
|
||||
onMoveUp={() => handleMoveItem(index, index - 1)}
|
||||
onMoveDown={() => handleMoveItem(index, index + 1)}
|
||||
isFirst={index === 0}
|
||||
isLast={index === titles.length - 1}
|
||||
disabled={isSubmitting}
|
||||
size="xs"
|
||||
/>
|
||||
|
||||
{/* 순서 번호 */}
|
||||
<span className="text-sm text-muted-foreground w-8">
|
||||
@@ -295,7 +333,7 @@ export function TitleManagement() {
|
||||
|
||||
{/* 안내 문구 */}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
※ 직책 순서는 드래그 앤 드롭으로 변경할 수 있습니다.
|
||||
※ 화살표 버튼으로 순서를 변경할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user