auto commit

This commit is contained in:
bangdk 2024-11-06 16:01:13 +09:00
parent 19f82aca2b
commit 304318af4e
11 changed files with 181 additions and 136 deletions

View File

@ -126,22 +126,27 @@ const AccountsPage = () => {
// Table columns // Table columns
const columns: ColumnDef<User>[] = [ const columns: ColumnDef<User>[] = [
{ // {
accessorKey: "username", // accessorKey: "username",
header: "아이디", // header: "아이디",
}, // },
{ {
accessorKey: "name", accessorKey: "name",
header: "이름", header: "이름",
}, },
{ {
accessorKey: "email", accessorKey: "Department.name",
header: "이메일", header: "부서",
}, cell: ({ row }) => row.original.Department?.name || "-",
{
accessorKey: "phone",
header: "전화번호",
}, },
// {
// accessorKey: "email",
// header: "이메일",
// },
// {
// accessorKey: "phone",
// header: "전화번호",
// },
{ {
accessorKey: "role", accessorKey: "role",
header: "역할", header: "역할",
@ -164,6 +169,16 @@ const AccountsPage = () => {
return (roles ?? []).map((role) => role.name).join(", "); 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", accessorKey: "isActive",
header: "활성화", 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", id: "actions",
header: "액션", header: "액션",

View File

@ -1,5 +1,5 @@
// src/app/(admin)/users/roles/components/RoleForm.tsx // 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 { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod"; import * as z from "zod";
@ -17,18 +17,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Role } from "@/types/user";
interface Permission {
[key: string]: string[];
}
interface Role {
id: string;
name: string;
description: string;
permissions: Permission;
isActive: boolean;
}
const formSchema = z.object({ const formSchema = z.object({
name: z name: z
@ -66,38 +55,55 @@ export const RoleForm = ({
onSubmit, onSubmit,
onCancel, onCancel,
}: RoleFormProps) => { }: RoleFormProps) => {
const form = useForm<z.infer<typeof formSchema>>({ const [mounted, setMounted] = useState(false);
resolver: zodResolver(formSchema),
defaultValues: { // 초기값 설정을 별도 상수로 분리
name: initialData?.name || "", const defaultPermissions = {
description: initialData?.description || "",
permissions: initialData?.permissions || {
company: [], company: [],
user: [], user: [],
energy: [], energy: [],
equipment: [], equipment: [],
report: [], report: [],
}, };
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: initialData?.name || "",
description: initialData?.description || "",
permissions: initialData?.permissions || defaultPermissions,
isActive: initialData?.isActive ?? true, 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 = ( const handlePermissionChange = (
category: string, category: string,
type: string, type: string,
checked: boolean checked: boolean
) => { ) => {
const currentPermissions = form.getValues().permissions[category] || []; const currentPermissions = permissions[category] || [];
let newPermissions; 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, { form.setValue(`permissions.${category}`, newPermissions, {
shouldValidate: true, shouldValidate: true,
shouldDirty: 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 ( return (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
@ -148,33 +186,8 @@ export const RoleForm = ({
<CardContent className="pt-4"> <CardContent className="pt-4">
<div className="font-medium mb-2">{categoryName}</div> <div className="font-medium mb-2">{categoryName}</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{Object.entries(PERMISSION_TYPES).map( {Object.entries(PERMISSION_TYPES).map(([type, typeName]) =>
([type, typeName]) => ( renderPermissionCheckbox(category, 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>
)
)} )}
</div> </div>
</CardContent> </CardContent>
@ -205,12 +218,21 @@ export const RoleForm = ({
/> />
<div className="flex justify-end space-x-2"> <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>
<Button type="submit">{initialData ? "수정" : "생성"}</Button> <Button type="submit" className="w-24">
{initialData ? "수정" : "생성"}
</Button>
</div> </div>
</form> </form>
</Form> </Form>
); );
}; };
export default RoleForm;

View File

@ -19,18 +19,7 @@ import { RoleForm } from "./components/RoleForm";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { Role } from "@/types/user";
interface Permission {
[key: string]: string[];
}
interface Role {
id: string;
name: string;
description: string;
permissions: Permission;
isActive: boolean;
}
const RolesPage = () => { const RolesPage = () => {
const { token, user } = useAuthStore(); const { token, user } = useAuthStore();
@ -234,7 +223,7 @@ const RolesPage = () => {
</div> </div>
{/* 중첩된 Dialog를 제거하고 하나의 Dialog만 사용 */} {/* 중첩된 Dialog를 제거하고 하나의 Dialog만 사용 */}
<Dialog open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{editingRole ? "권한 그룹 수정" : "새 권한 그룹"} {editingRole ? "권한 그룹 수정" : "새 권한 그룹"}

View File

@ -191,7 +191,14 @@ export function EquipmentForm({
// 사양 정보 상태 추가 // 사양 정보 상태 추가
const [specifications, setSpecifications] = React.useState< const [specifications, setSpecifications] = React.useState<
Record<string, string> 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) => { const handleSpecificationChange = (key: string, value: string) => {

View File

@ -100,10 +100,10 @@ const InventoryPage = () => {
accessorKey: "model", accessorKey: "model",
header: "모델명", header: "모델명",
}, },
{ // {
accessorKey: "manufacturer", // accessorKey: "manufacturer",
header: "제조사", // header: "제조사",
}, // },
{ {
accessorKey: "type", accessorKey: "type",
header: "유형", header: "유형",
@ -119,12 +119,12 @@ const InventoryPage = () => {
return typeMap[row.original.type] || row.original.type; return typeMap[row.original.type] || row.original.type;
}, },
}, },
{ // {
accessorKey: "installationDate", // accessorKey: "installationDate",
header: "설치일", // header: "설치일",
cell: ({ row }) => // cell: ({ row }) =>
new Date(row.original.installationDate).toLocaleDateString(), // new Date(row.original.installationDate).toLocaleDateString(),
}, // },
{ {
accessorKey: "lastMaintenance", accessorKey: "lastMaintenance",
header: "최근 정비일", header: "최근 정비일",

View File

@ -60,6 +60,15 @@ const MaintenancePage = () => {
// Table columns // Table columns
const columns: ColumnDef<MaintenanceLog>[] = [ const columns: ColumnDef<MaintenanceLog>[] = [
{
accessorKey: "equipment.name",
header: "설비명",
cell: ({ row }) => row.original.Equipment?.name || "-",
},
{
accessorKey: "description",
header: "정비 내용",
},
{ {
accessorKey: "type", accessorKey: "type",
header: "정비 유형", header: "정비 유형",
@ -100,15 +109,6 @@ const MaintenancePage = () => {
? new Date(row.original.completionDate).toLocaleDateString() ? new Date(row.original.completionDate).toLocaleDateString()
: "-", : "-",
}, },
{
accessorKey: "equipment.name",
header: "설비명",
cell: ({ row }) => row.original.Equipment?.name || "-",
},
{
accessorKey: "description",
header: "정비 내용",
},
// { // {
// accessorKey: "isActive", // accessorKey: "isActive",
// header: "활성 상태", // header: "활성 상태",

View File

@ -144,10 +144,10 @@ const PartsPage = () => {
</Badge> </Badge>
), ),
}, },
{ // {
accessorKey: "manufacturer", // accessorKey: "manufacturer",
header: "제조사", // header: "제조사",
}, // },
{ {
accessorKey: "stockQuantity", accessorKey: "stockQuantity",
header: "재고수량", header: "재고수량",
@ -276,9 +276,16 @@ const PartsPage = () => {
initialData={editingPart || undefined} initialData={editingPart || undefined}
onSubmit={(data) => { onSubmit={(data) => {
if (editingPart) { if (editingPart) {
updateMutation.mutate({ id: editingPart.id, ...data }); updateMutation.mutate({
id: editingPart.id,
...data,
specifications: JSON.stringify(data.specifications || {}),
});
} else { } else {
createMutation.mutate(data); createMutation.mutate({
...data,
specifications: JSON.stringify(data.specifications || {}),
});
} }
}} }}
onCancel={() => { onCancel={() => {

View File

@ -7,7 +7,7 @@ import { useForm, useFieldArray } from "react-hook-form";
import * as z from "zod"; import * as z from "zod";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Award, Plus, X } from "lucide-react"; import { Award, X } from "lucide-react";
import { import {
Form, Form,
FormControl, FormControl,
@ -64,7 +64,7 @@ const formSchema = z.object({
interface PersonnelFormProps { interface PersonnelFormProps {
initialData?: Personnel; initialData?: Personnel;
onSubmit: (data: any) => void; onSubmit: (data: z.infer<typeof formSchema>) => void;
onCancel: () => void; onCancel: () => void;
} }
@ -113,11 +113,7 @@ export function PersonnelForm({
}); });
// useFieldArray 설정 // useFieldArray 설정
const { useFieldArray({
fields: certFields,
append: appendCert,
remove: removeCert,
} = useFieldArray({
control: form.control, control: form.control,
name: "certifications", name: "certifications",
}); });
@ -200,7 +196,7 @@ export function PersonnelForm({
<SelectValue placeholder="부서를 선택하세요" /> <SelectValue placeholder="부서를 선택하세요" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{department?.map((dept: any) => ( {department?.map((dept: { id: string; name: string }) => (
<SelectItem key={dept.id} value={dept.name}> <SelectItem key={dept.id} value={dept.name}>
{dept.name} {dept.name}
</SelectItem> </SelectItem>

View File

@ -16,7 +16,7 @@ import {
DialogDescription, DialogDescription,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { useToast } from "@/hooks/use-toast"; 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 { ColumnDef } from "@tanstack/react-table";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
@ -157,7 +157,7 @@ const PersonnelPage = () => {
header: "자격증", header: "자격증",
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex flex-wrap gap-1"> <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"> <Badge key={index} variant="secondary">
{cert.name} {cert.name}
</Badge> </Badge>
@ -308,9 +308,26 @@ const PersonnelPage = () => {
initialData={editingPersonnel || undefined} initialData={editingPersonnel || undefined}
onSubmit={(data) => { onSubmit={(data) => {
if (editingPersonnel) { 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 { } 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={() => { onCancel={() => {

View File

@ -8,6 +8,7 @@ export interface Equipment {
manufacturer: string; manufacturer: string;
type: "HVAC" | "Boiler" | "Compressor" | "Motor" | "Pump" | "Other"; type: "HVAC" | "Boiler" | "Compressor" | "Motor" | "Pump" | "Other";
specifications: Record<string, string | number | boolean>; specifications: Record<string, string | number | boolean>;
defaultSpecifications: Record<string, string | number | boolean>;
installationDate: string; installationDate: string;
lastMaintenance: string | null; lastMaintenance: string | null;
isActive: boolean; isActive: boolean;

View File

@ -34,5 +34,11 @@ export interface User {
export interface Role { export interface Role {
id: string; id: string;
name: string; name: string;
description?: string; description: string;
permissions: Permission;
isActive: boolean;
}
export interface Permission {
[key: string]: string[];
} }