summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-06 09:52:27 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-06 09:52:27 +0000
commita4bf0d8376962c922da90cd08781893a3658ecc2 (patch)
tree1a5ba08de890482729c1eeba37f09f22142ed3f6
parenteb1ed7f7e807a4a550285064e96169621e011a42 (diff)
(최겸) 구매 보완 재실사 자동생성 삭제
-rw-r--r--lib/pq/pq-review-table-new/site-visit-dialog.tsx189
-rw-r--r--lib/site-visit/service.ts41
-rw-r--r--lib/vendor-investigation/service.ts14
3 files changed, 84 insertions, 160 deletions
diff --git a/lib/pq/pq-review-table-new/site-visit-dialog.tsx b/lib/pq/pq-review-table-new/site-visit-dialog.tsx
index a7cc3313..55468499 100644
--- a/lib/pq/pq-review-table-new/site-visit-dialog.tsx
+++ b/lib/pq/pq-review-table-new/site-visit-dialog.tsx
@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
-import { CalendarIcon, X, Plus, Trash2, Check, Search } from "lucide-react"
+import { CalendarIcon, X, Plus, Trash2 } from "lucide-react"
import { useForm, useFieldArray } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { format } from "date-fns"
@@ -36,18 +36,9 @@ import {
} from "@/components/ui/popover"
import { Checkbox } from "@/components/ui/checkbox"
import { Badge } from "@/components/ui/badge"
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from "@/components/ui/command"
import { toast } from "sonner"
import { getSiteVisitRequestAction, getUsersForSiteVisitAction } from "@/lib/site-visit/service"
-import { cn } from "@/lib/utils"
+import { UserCombobox } from "./user-combobox"
import {
Dropzone,
DropzoneDescription,
@@ -183,7 +174,7 @@ interface SiteVisitUser {
id: number;
name: string;
email: string;
- deptName: string | null;
+ department?: string;
}
// 참석자 섹션 컴포넌트
@@ -204,57 +195,55 @@ function AttendeeSection({
});
const isChecked = form.watch(`shiAttendees.${itemKey}.checked`);
- const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
- const [searchQuery, setSearchQuery] = React.useState("");
const [users, setUsers] = React.useState<SiteVisitUser[]>([]);
const [isLoadingUsers, setIsLoadingUsers] = React.useState(false);
+ const [selectedUserId, setSelectedUserId] = React.useState<number | null>(null);
- const loadUsers = React.useCallback(async () => {
- setIsLoadingUsers(true);
- try {
- const result = await getUsersForSiteVisitAction(
- searchQuery.trim() || undefined
- );
- if (result.success && result.data) {
- setUsers(result.data);
- }
- } catch (error) {
- console.error("사용자 목록 로드 오류:", error);
- toast.error("사용자 목록을 불러오는데 실패했습니다.");
- } finally {
- setIsLoadingUsers(false);
- }
- }, [searchQuery]);
-
- // 사용자 목록 가져오기
- React.useEffect(() => {
- if (isPopoverOpen && isChecked) {
- loadUsers();
- }
- }, [isPopoverOpen, isChecked, loadUsers]);
-
- // 검색 쿼리 변경 시 사용자 목록 다시 로드 (debounce)
+ // Dialog가 열릴 때 사용자 목록 로드
React.useEffect(() => {
- if (!isPopoverOpen || !isChecked) return;
+ if (isChecked && users.length === 0) {
+ const loadUsers = async () => {
+ setIsLoadingUsers(true);
+ try {
+ const result = await getUsersForSiteVisitAction();
+ if (result.success && result.data) {
+ setUsers(result.data.map((user: any) => ({
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ department: user.deptName || undefined,
+ })));
+ }
+ } catch (error) {
+ console.error("사용자 목록 로드 오류:", error);
+ toast.error("사용자 목록을 불러오는데 실패했습니다.");
+ } finally {
+ setIsLoadingUsers(false);
+ }
+ };
- const timer = setTimeout(() => {
loadUsers();
- }, 300);
+ }
+ }, [isChecked, users.length]);
- return () => clearTimeout(timer);
- }, [searchQuery, isPopoverOpen, isChecked, loadUsers]);
+ const handleUserSelect = (userId: number) => {
+ // 선택된 사용자 정보 찾기
+ const selectedUser = users.find(user => user.id === userId);
+ if (!selectedUser) return;
- const handleUserSelect = (user: SiteVisitUser) => {
// 현재 폼의 attendees 값 가져오기
const currentAttendees = form.getValues(`shiAttendees.${itemKey}.attendees`) as Array<{
name: string;
department?: string;
email: string;
- }>;
+ }> | undefined;
- // 이미 선택된 사용자인지 확인
- const existingIndex = currentAttendees.findIndex(
- (attendee) => attendee.email === user.email
+ // undefined이거나 배열이 아닌 경우 빈 배열로 처리
+ const attendees = Array.isArray(currentAttendees) ? currentAttendees : [];
+
+ // 이미 선택된 사용자인지 확인 (이메일 기준)
+ const existingIndex = attendees.findIndex(
+ (attendee) => attendee.email === selectedUser.email
);
if (existingIndex >= 0) {
@@ -263,20 +252,14 @@ function AttendeeSection({
} else {
// 새로 추가
append({
- name: user.name,
- department: user.deptName || "",
- email: user.email,
+ name: selectedUser.name,
+ department: selectedUser.department || "",
+ email: selectedUser.email,
});
}
- };
-
- const isUserSelected = (userEmail: string) => {
- const currentAttendees = form.getValues(`shiAttendees.${itemKey}.attendees`) as Array<{
- name: string;
- department?: string;
- email: string;
- }>;
- return currentAttendees.some((attendee) => attendee.email === userEmail);
+
+ // 선택 초기화
+ setSelectedUserId(null);
};
return (
@@ -296,7 +279,6 @@ function AttendeeSection({
// 체크 해제 시 참석자 목록 초기화
if (!checked) {
form.setValue(`shiAttendees.${itemKey}.attendees` as any, []);
- setIsPopoverOpen(false);
}
}}
disabled={isPending}
@@ -319,68 +301,27 @@ function AttendeeSection({
{isChecked && (
<div className="space-y-3">
{/* 사용자 선택 UI */}
- <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
- <PopoverTrigger asChild>
- <Button
- type="button"
- variant="outline"
- className="w-full justify-start text-left font-normal"
- disabled={isPending}
- >
- <Search className="mr-2 h-4 w-4" />
- 이름 또는 이메일로 검색...
- </Button>
- </PopoverTrigger>
- <PopoverContent className="w-[400px] p-0" align="start">
- <Command>
- <CommandInput
- placeholder="이름 또는 이메일로 검색..."
- value={searchQuery}
- onValueChange={setSearchQuery}
- />
- <CommandList>
- <CommandEmpty>
- {isLoadingUsers ? "로딩 중..." : "검색 결과가 없습니다."}
- </CommandEmpty>
- <CommandGroup>
- {users.map((user) => {
- const selected = isUserSelected(user.email);
- return (
- <CommandItem
- key={user.id}
- value={`${user.name} ${user.email}`}
- onSelect={() => handleUserSelect(user)}
- className="cursor-pointer"
- >
- <Check
- className={cn(
- "mr-2 h-4 w-4",
- selected ? "opacity-100" : "opacity-0"
- )}
- />
- <div className="flex flex-col flex-1 min-w-0">
- <div className="flex items-center gap-2">
- <span className="font-medium truncate">
- {user.name}
- </span>
- {user.deptName && (
- <span className="text-xs text-muted-foreground truncate">
- ({user.deptName})
- </span>
- )}
- </div>
- <span className="text-xs text-muted-foreground truncate">
- {user.email}
- </span>
- </div>
- </CommandItem>
- );
- })}
- </CommandGroup>
- </CommandList>
- </Command>
- </PopoverContent>
- </Popover>
+ <div className="flex items-center gap-2">
+ <div className="flex-1">
+ <UserCombobox
+ users={users}
+ value={selectedUserId}
+ onChange={handleUserSelect}
+ placeholder={isLoadingUsers ? "담당자 로딩 중..." : "담당자 선택..."}
+ disabled={isPending || isLoadingUsers}
+ />
+ </div>
+ <Button
+ type="button"
+ variant="outline"
+ size="icon"
+ onClick={() => setSelectedUserId(null)}
+ disabled={isPending || !selectedUserId}
+ title="선택 초기화"
+ >
+ <X className="h-4 w-4" />
+ </Button>
+ </div>
{/* 선택된 사용자 목록 */}
{fields.length > 0 && (
diff --git a/lib/site-visit/service.ts b/lib/site-visit/service.ts
index d78682b5..684e73f1 100644
--- a/lib/site-visit/service.ts
+++ b/lib/site-visit/service.ts
@@ -877,22 +877,9 @@ export async function getSiteVisitRequestAction(investigationId: number) {
}
// domain이 'partners'가 아닌 사용자 목록 가져오기
- export async function getUsersForSiteVisitAction(searchQuery?: string) {
+ export async function getUsersForSiteVisitAction() {
try {
- let whereCondition = ne(users.domain, "partners");
-
- // 검색 쿼리가 있으면 이름 또는 이메일로 필터링
- if (searchQuery && searchQuery.trim()) {
- const searchPattern = `%${searchQuery.trim()}%`;
- whereCondition = and(
- ne(users.domain, "partners"),
- or(
- ilike(users.name, searchPattern),
- ilike(users.email, searchPattern)
- )
- ) as any;
- }
-
+ // domain이 'partners'가 아니고, isActive가 true인 사용자만 조회
const userList = await db
.select({
id: users.id,
@@ -901,20 +888,24 @@ export async function getSiteVisitRequestAction(investigationId: number) {
deptName: users.deptName,
})
.from(users)
- .where(whereCondition)
- .orderBy(users.name)
- .limit(100); // 최대 100명까지
-
+ .where(
+ and(
+ eq(users.isActive, true),
+ ne(users.domain, "partners")
+ )
+ )
+ .orderBy(users.name);
+
return {
- success: true,
data: userList,
- };
+ success: true
+ }
} catch (error) {
- console.error("사용자 목록 조회 오류:", error);
+ console.error("사용자 목록 조회 오류:", error)
return {
- success: false,
- error: "사용자 목록 조회 중 오류가 발생했습니다.",
data: [],
- };
+ success: false,
+ error: error instanceof Error ? error.message : "사용자 목록을 가져오는 중 오류가 발생했습니다."
+ }
}
} \ No newline at end of file
diff --git a/lib/vendor-investigation/service.ts b/lib/vendor-investigation/service.ts
index 39984661..cf37ad06 100644
--- a/lib/vendor-investigation/service.ts
+++ b/lib/vendor-investigation/service.ts
@@ -382,7 +382,7 @@ export async function updateVendorInvestigationResultAction(formData: FormData)
.set(updateData)
.where(eq(vendorInvestigations.id, parsed.investigationId))
/*
- 현재 보완 프로세스는 자동으로 처리됨. 만약 dialog 필요하면 아래 서버액션 분기 필요.(1029/최겸)
+ 현재 보완-서류제출 프로세스는 자동으로 처리됨. 만약 dialog 필요하면 아래 서버액션 분기 필요.(1029/최겸)
*/
// 5-1) 보완 프로세스 자동 처리 (TO-BE)
if (parsed.evaluationResult === "SUPPLEMENT_REINSPECT" || parsed.evaluationResult === "SUPPLEMENT_DOCUMENT") {
@@ -396,16 +396,7 @@ export async function updateVendorInvestigationResultAction(formData: FormData)
.then(rows => rows[0]);
if (investigation?.investigationMethod === "PRODUCT_INSPECTION" || investigation?.investigationMethod === "SITE_VISIT_EVAL") {
- if (parsed.evaluationResult === "SUPPLEMENT_REINSPECT") {
- // 보완-재실사 요청 자동 생성
- await requestSupplementReinspectionAction({
- investigationId: parsed.investigationId,
- siteVisitData: {
- inspectionDuration: 1.0, // 기본 1일
- additionalRequests: "보완을 위한 재실사 요청입니다.",
- }
- });
- } else if (parsed.evaluationResult === "SUPPLEMENT_DOCUMENT") {
+ if (parsed.evaluationResult === "SUPPLEMENT_DOCUMENT") {
// 보완-서류제출 요청 자동 생성
await requestSupplementDocumentAction({
investigationId: parsed.investigationId,
@@ -421,6 +412,7 @@ export async function updateVendorInvestigationResultAction(formData: FormData)
// 6) 캐시 무효화
revalidateTag("vendor-investigations")
revalidatePath("/evcp/vendor-investigation")
+ revalidatePath("/evcp/pq_new")
return { success: true }
} catch (error) {