auto commit
This commit is contained in:
parent
19f82aca2b
commit
304318af4e
@ -126,22 +126,27 @@ const AccountsPage = () => {
|
||||
|
||||
// Table columns
|
||||
const columns: ColumnDef<User>[] = [
|
||||
{
|
||||
accessorKey: "username",
|
||||
header: "아이디",
|
||||
},
|
||||
// {
|
||||
// accessorKey: "username",
|
||||
// header: "아이디",
|
||||
// },
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "이름",
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
header: "이메일",
|
||||
},
|
||||
{
|
||||
accessorKey: "phone",
|
||||
header: "전화번호",
|
||||
accessorKey: "Department.name",
|
||||
header: "부서",
|
||||
cell: ({ row }) => row.original.Department?.name || "-",
|
||||
},
|
||||
// {
|
||||
// accessorKey: "email",
|
||||
// header: "이메일",
|
||||
// },
|
||||
// {
|
||||
// accessorKey: "phone",
|
||||
// header: "전화번호",
|
||||
// },
|
||||
{
|
||||
accessorKey: "role",
|
||||
header: "역할",
|
||||
@ -164,6 +169,16 @@ const AccountsPage = () => {
|
||||
return (roles ?? []).map((role) => role.name).join(", ");
|
||||
},
|
||||
},
|
||||
// {
|
||||
// accessorKey: "Company.name",
|
||||
// header: "회사",
|
||||
// cell: ({ row }) => row.original.Company?.name || "-",
|
||||
// },
|
||||
// {
|
||||
// accessorKey: "Branch.name",
|
||||
// header: "지점",
|
||||
// cell: ({ row }) => row.original.Branch?.name || "-",
|
||||
// },
|
||||
{
|
||||
accessorKey: "isActive",
|
||||
header: "활성화",
|
||||
@ -176,21 +191,6 @@ const AccountsPage = () => {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "Company.name",
|
||||
header: "회사",
|
||||
cell: ({ row }) => row.original.Company?.name || "-",
|
||||
},
|
||||
{
|
||||
accessorKey: "Branch.name",
|
||||
header: "지점",
|
||||
cell: ({ row }) => row.original.Branch?.name || "-",
|
||||
},
|
||||
{
|
||||
accessorKey: "Department.name",
|
||||
header: "부서",
|
||||
cell: ({ row }) => row.original.Department?.name || "-",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "액션",
|
||||
|
@ -1,5 +1,5 @@
|
||||
// src/app/(admin)/users/roles/components/RoleForm.tsx
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
@ -17,18 +17,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
interface Permission {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission;
|
||||
isActive: boolean;
|
||||
}
|
||||
import { Role } from "@/types/user";
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z
|
||||
@ -66,38 +55,55 @@ export const RoleForm = ({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: RoleFormProps) => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// 초기값 설정을 별도 상수로 분리
|
||||
const defaultPermissions = {
|
||||
company: [],
|
||||
user: [],
|
||||
energy: [],
|
||||
equipment: [],
|
||||
report: [],
|
||||
};
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: initialData?.name || "",
|
||||
description: initialData?.description || "",
|
||||
permissions: initialData?.permissions || {
|
||||
company: [],
|
||||
user: [],
|
||||
energy: [],
|
||||
equipment: [],
|
||||
report: [],
|
||||
},
|
||||
permissions: initialData?.permissions || defaultPermissions,
|
||||
isActive: initialData?.isActive ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
// handlePermissionChange 함수 수정
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
// initialData가 있을 경우, 폼 값을 초기화
|
||||
if (initialData) {
|
||||
Object.entries(initialData.permissions).forEach(
|
||||
([category, permissions]) => {
|
||||
form.setValue(`permissions.${category}`, permissions, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [initialData, form]);
|
||||
|
||||
// permissions 값을 watch로 관찰
|
||||
const permissions = form.watch("permissions");
|
||||
|
||||
const handlePermissionChange = (
|
||||
category: string,
|
||||
type: string,
|
||||
checked: boolean
|
||||
) => {
|
||||
const currentPermissions = form.getValues().permissions[category] || [];
|
||||
let newPermissions;
|
||||
const currentPermissions = permissions[category] || [];
|
||||
const newPermissions = checked
|
||||
? [...currentPermissions, type]
|
||||
: currentPermissions.filter((p) => p !== type);
|
||||
|
||||
if (checked) {
|
||||
newPermissions = [...currentPermissions, type];
|
||||
} else {
|
||||
newPermissions = currentPermissions.filter((p) => p !== type);
|
||||
}
|
||||
|
||||
// form.setValue 대신 setValue를 사용하고 shouldValidate를 true로 설정
|
||||
form.setValue(`permissions.${category}`, newPermissions, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
@ -105,6 +111,38 @@ export const RoleForm = ({
|
||||
});
|
||||
};
|
||||
|
||||
const renderPermissionCheckbox = (
|
||||
category: string,
|
||||
type: string,
|
||||
typeName: string
|
||||
) => {
|
||||
if (!mounted && !initialData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isChecked = permissions[category]?.includes(type);
|
||||
const defaultChecked = initialData?.permissions[category]?.includes(type);
|
||||
|
||||
return (
|
||||
<div key={`${category}-${type}`} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`${category}-${type}`}
|
||||
checked={isChecked}
|
||||
defaultChecked={defaultChecked}
|
||||
onCheckedChange={(checked) =>
|
||||
handlePermissionChange(category, type, checked as boolean)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${category}-${type}`}
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{typeName}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
@ -148,33 +186,8 @@ export const RoleForm = ({
|
||||
<CardContent className="pt-4">
|
||||
<div className="font-medium mb-2">{categoryName}</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{Object.entries(PERMISSION_TYPES).map(
|
||||
([type, typeName]) => (
|
||||
<div
|
||||
key={`${category}-${type}`}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Checkbox
|
||||
id={`${category}-${type}`}
|
||||
checked={form
|
||||
.watch(`permissions.${category}`, [])
|
||||
.includes(type)}
|
||||
onCheckedChange={(checked) =>
|
||||
handlePermissionChange(
|
||||
category,
|
||||
type,
|
||||
checked as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${category}-${type}`}
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{typeName}
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
{Object.entries(PERMISSION_TYPES).map(([type, typeName]) =>
|
||||
renderPermissionCheckbox(category, type, typeName)
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -205,12 +218,21 @@ export const RoleForm = ({
|
||||
/>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
className="w-24"
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="submit">{initialData ? "수정" : "생성"}</Button>
|
||||
<Button type="submit" className="w-24">
|
||||
{initialData ? "수정" : "생성"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoleForm;
|
||||
|
@ -19,18 +19,7 @@ import { RoleForm } from "./components/RoleForm";
|
||||
import { api } from "@/lib/api";
|
||||
import { AxiosError } from "axios";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
interface Permission {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission;
|
||||
isActive: boolean;
|
||||
}
|
||||
import { Role } from "@/types/user";
|
||||
|
||||
const RolesPage = () => {
|
||||
const { token, user } = useAuthStore();
|
||||
@ -234,7 +223,7 @@ const RolesPage = () => {
|
||||
</div>
|
||||
{/* 중첩된 Dialog를 제거하고 하나의 Dialog만 사용 */}
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{editingRole ? "권한 그룹 수정" : "새 권한 그룹"}
|
||||
|
@ -191,7 +191,14 @@ export function EquipmentForm({
|
||||
// 사양 정보 상태 추가
|
||||
const [specifications, setSpecifications] = React.useState<
|
||||
Record<string, string>
|
||||
>(initialData?.specifications || defaultSpecifications);
|
||||
>(
|
||||
Object.fromEntries(
|
||||
Object.entries(initialData?.specifications || {}).map(([k, v]) => [
|
||||
k,
|
||||
String(v),
|
||||
])
|
||||
) || defaultSpecifications
|
||||
);
|
||||
|
||||
// 사양 정보 필드 추가/제거 핸들러 수정
|
||||
const handleSpecificationChange = (key: string, value: string) => {
|
||||
|
@ -100,10 +100,10 @@ const InventoryPage = () => {
|
||||
accessorKey: "model",
|
||||
header: "모델명",
|
||||
},
|
||||
{
|
||||
accessorKey: "manufacturer",
|
||||
header: "제조사",
|
||||
},
|
||||
// {
|
||||
// accessorKey: "manufacturer",
|
||||
// header: "제조사",
|
||||
// },
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "유형",
|
||||
@ -119,12 +119,12 @@ const InventoryPage = () => {
|
||||
return typeMap[row.original.type] || row.original.type;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "installationDate",
|
||||
header: "설치일",
|
||||
cell: ({ row }) =>
|
||||
new Date(row.original.installationDate).toLocaleDateString(),
|
||||
},
|
||||
// {
|
||||
// accessorKey: "installationDate",
|
||||
// header: "설치일",
|
||||
// cell: ({ row }) =>
|
||||
// new Date(row.original.installationDate).toLocaleDateString(),
|
||||
// },
|
||||
{
|
||||
accessorKey: "lastMaintenance",
|
||||
header: "최근 정비일",
|
||||
|
@ -60,6 +60,15 @@ const MaintenancePage = () => {
|
||||
|
||||
// Table columns
|
||||
const columns: ColumnDef<MaintenanceLog>[] = [
|
||||
{
|
||||
accessorKey: "equipment.name",
|
||||
header: "설비명",
|
||||
cell: ({ row }) => row.original.Equipment?.name || "-",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "정비 내용",
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "정비 유형",
|
||||
@ -100,15 +109,6 @@ const MaintenancePage = () => {
|
||||
? new Date(row.original.completionDate).toLocaleDateString()
|
||||
: "-",
|
||||
},
|
||||
{
|
||||
accessorKey: "equipment.name",
|
||||
header: "설비명",
|
||||
cell: ({ row }) => row.original.Equipment?.name || "-",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "정비 내용",
|
||||
},
|
||||
// {
|
||||
// accessorKey: "isActive",
|
||||
// header: "활성 상태",
|
||||
|
@ -144,10 +144,10 @@ const PartsPage = () => {
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "manufacturer",
|
||||
header: "제조사",
|
||||
},
|
||||
// {
|
||||
// accessorKey: "manufacturer",
|
||||
// header: "제조사",
|
||||
// },
|
||||
{
|
||||
accessorKey: "stockQuantity",
|
||||
header: "재고수량",
|
||||
@ -276,9 +276,16 @@ const PartsPage = () => {
|
||||
initialData={editingPart || undefined}
|
||||
onSubmit={(data) => {
|
||||
if (editingPart) {
|
||||
updateMutation.mutate({ id: editingPart.id, ...data });
|
||||
updateMutation.mutate({
|
||||
id: editingPart.id,
|
||||
...data,
|
||||
specifications: JSON.stringify(data.specifications || {}),
|
||||
});
|
||||
} else {
|
||||
createMutation.mutate(data);
|
||||
createMutation.mutate({
|
||||
...data,
|
||||
specifications: JSON.stringify(data.specifications || {}),
|
||||
});
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
|
@ -7,7 +7,7 @@ import { useForm, useFieldArray } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Award, Plus, X } from "lucide-react";
|
||||
import { Award, X } from "lucide-react";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -64,7 +64,7 @@ const formSchema = z.object({
|
||||
|
||||
interface PersonnelFormProps {
|
||||
initialData?: Personnel;
|
||||
onSubmit: (data: any) => void;
|
||||
onSubmit: (data: z.infer<typeof formSchema>) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@ -113,11 +113,7 @@ export function PersonnelForm({
|
||||
});
|
||||
|
||||
// useFieldArray 설정
|
||||
const {
|
||||
fields: certFields,
|
||||
append: appendCert,
|
||||
remove: removeCert,
|
||||
} = useFieldArray({
|
||||
useFieldArray({
|
||||
control: form.control,
|
||||
name: "certifications",
|
||||
});
|
||||
@ -200,7 +196,7 @@ export function PersonnelForm({
|
||||
<SelectValue placeholder="부서를 선택하세요" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{department?.map((dept: any) => (
|
||||
{department?.map((dept: { id: string; name: string }) => (
|
||||
<SelectItem key={dept.id} value={dept.name}>
|
||||
{dept.name}
|
||||
</SelectItem>
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Plus, Edit, Trash2, Wrench, Search } from "lucide-react";
|
||||
import { Plus, Edit, Trash2, Search } from "lucide-react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { AxiosError } from "axios";
|
||||
@ -157,7 +157,7 @@ const PersonnelPage = () => {
|
||||
header: "자격증",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{row.original.certifications?.map((cert: any, index: number) => (
|
||||
{row.original.certifications?.map((cert: { name: string }, index: number) => (
|
||||
<Badge key={index} variant="secondary">
|
||||
{cert.name}
|
||||
</Badge>
|
||||
@ -308,9 +308,26 @@ const PersonnelPage = () => {
|
||||
initialData={editingPersonnel || undefined}
|
||||
onSubmit={(data) => {
|
||||
if (editingPersonnel) {
|
||||
updateMutation.mutate({ id: editingPersonnel.id, ...data });
|
||||
updateMutation.mutate({
|
||||
id: editingPersonnel.id,
|
||||
...data,
|
||||
certifications: data.certifications?.map(cert => ({
|
||||
name: cert.name,
|
||||
date: cert.issueDate,
|
||||
expiry: cert.expiryDate || "",
|
||||
expiryDate: cert.expiryDate || "",
|
||||
})),
|
||||
});
|
||||
} else {
|
||||
createMutation.mutate(data);
|
||||
createMutation.mutate({
|
||||
...data,
|
||||
certifications: data.certifications?.map(cert => ({
|
||||
name: cert.name,
|
||||
date: cert.issueDate,
|
||||
expiry: cert.expiryDate || "",
|
||||
expiryDate: cert.expiryDate || "",
|
||||
})),
|
||||
});
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
|
@ -8,6 +8,7 @@ export interface Equipment {
|
||||
manufacturer: string;
|
||||
type: "HVAC" | "Boiler" | "Compressor" | "Motor" | "Pump" | "Other";
|
||||
specifications: Record<string, string | number | boolean>;
|
||||
defaultSpecifications: Record<string, string | number | boolean>;
|
||||
installationDate: string;
|
||||
lastMaintenance: string | null;
|
||||
isActive: boolean;
|
||||
|
@ -34,5 +34,11 @@ export interface User {
|
||||
export interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
description: string;
|
||||
permissions: Permission;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface Permission {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user