diff --git a/backend/src/main/java/com/srsystem/controller/CommonCodeController.java b/backend/src/main/java/com/srsystem/controller/CommonCodeController.java index ca7e0ef..f45d198 100644 --- a/backend/src/main/java/com/srsystem/controller/CommonCodeController.java +++ b/backend/src/main/java/com/srsystem/controller/CommonCodeController.java @@ -3,6 +3,7 @@ package com.srsystem.controller; import com.srsystem.dto.ApiResponse; import com.srsystem.dto.CommonCodeRequest; import com.srsystem.entity.CommonCode; +import com.srsystem.entity.CommonCodeGroup; import com.srsystem.service.CommonCodeService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -13,6 +14,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; @Tag(name = "Common Code", description = "공통 코드 관리 API") @RestController @@ -33,8 +35,8 @@ public class CommonCodeController { @Operation(summary = "공통 코드 그룹 목록 조회", description = "모든 공통 코드 그룹 목록을 조회합니다") @GetMapping("/groups") @PreAuthorize("hasAnyRole('EMPLOYEE', 'MANAGER', 'ADMIN')") - public ResponseEntity>> getGroups() { - List groups = commonCodeService.getGroupCodes(); + public ResponseEntity>> getGroups() { + List groups = commonCodeService.getGroups(); return ResponseEntity.ok(ApiResponse.ok(groups)); } @@ -61,4 +63,40 @@ public class CommonCodeController { commonCodeService.deleteCode(id); return ResponseEntity.ok(ApiResponse.ok("공통 코드가 삭제되었습니다.", null)); } + + // ---- Group CRUD ---- + + @Operation(summary = "그룹 생성", description = "새로운 공통 코드 그룹을 생성합니다 (ADMIN 전용)") + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("/groups") + public ResponseEntity> createGroup(@RequestBody Map body) { + CommonCodeGroup group = commonCodeService.createGroup( + body.get("groupCode"), + body.get("groupName"), + body.get("description"), + body.get("sortOrder") != null ? Integer.parseInt(body.get("sortOrder")) : null + ); + return ResponseEntity.ok(ApiResponse.ok("그룹이 생성되었습니다.", group)); + } + + @Operation(summary = "그룹 수정", description = "공통 코드 그룹을 수정합니다 (ADMIN 전용)") + @PreAuthorize("hasRole('ADMIN')") + @PutMapping("/groups/{groupCode}") + public ResponseEntity> updateGroup(@PathVariable String groupCode, @RequestBody Map body) { + CommonCodeGroup group = commonCodeService.updateGroup( + groupCode, + body.get("groupName"), + body.get("description"), + body.get("sortOrder") != null ? Integer.parseInt(body.get("sortOrder")) : null + ); + return ResponseEntity.ok(ApiResponse.ok("그룹이 수정되었습니다.", group)); + } + + @Operation(summary = "그룹 삭제", description = "공통 코드 그룹을 삭제합니다 (ADMIN 전용, 하위 코드가 없을 때만 가능)") + @PreAuthorize("hasRole('ADMIN')") + @DeleteMapping("/groups/{groupCode}") + public ResponseEntity> deleteGroup(@PathVariable String groupCode) { + commonCodeService.deleteGroup(groupCode); + return ResponseEntity.ok(ApiResponse.ok("그룹이 삭제되었습니다.", null)); + } } diff --git a/backend/src/main/java/com/srsystem/entity/CommonCodeGroup.java b/backend/src/main/java/com/srsystem/entity/CommonCodeGroup.java new file mode 100644 index 0000000..dbc0cb2 --- /dev/null +++ b/backend/src/main/java/com/srsystem/entity/CommonCodeGroup.java @@ -0,0 +1,36 @@ +package com.srsystem.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; + +@Entity +@Table(name = "tb_common_code_group") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@SQLDelete(sql = "UPDATE tb_common_code_group SET is_deleted = 'Y' WHERE id = ?") +@SQLRestriction("is_deleted = 'N'") +public class CommonCodeGroup extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_common_code_groups") + @SequenceGenerator(name = "sq_common_code_groups", sequenceName = "sq_common_code_groups", allocationSize = 50) + @Column(name = "id") + private Long id; + + @Column(name = "group_code", nullable = false, unique = true, length = 50) + private String groupCode; + + @Column(name = "group_name", nullable = false, length = 255) + private String groupName; + + @Column(name = "description", length = 500) + private String description; + + @Column(name = "sort_order") + private Integer sortOrder; +} diff --git a/backend/src/main/java/com/srsystem/exception/ErrorCode.java b/backend/src/main/java/com/srsystem/exception/ErrorCode.java index e3a4a70..2cc582a 100644 --- a/backend/src/main/java/com/srsystem/exception/ErrorCode.java +++ b/backend/src/main/java/com/srsystem/exception/ErrorCode.java @@ -24,6 +24,8 @@ public enum ErrorCode { DISPATCH_NOT_FOUND(HttpStatus.NOT_FOUND, "DSP_ERR_001", "파견 이력을 찾을 수 없습니다"), AUTH_INVALID_CREDENTIALS(HttpStatus.BAD_REQUEST, "AUTH_ERR_003", "현재 비밀번호가 일치하지 않습니다"), CODE_NOT_FOUND(HttpStatus.NOT_FOUND, "COD_ERR_001", "공통코드를 찾을 수 없습니다"), + DUPLICATE_GROUP_CODE(HttpStatus.CONFLICT, "COD_ERR_002", "이미 존재하는 그룹 코드입니다"), + GROUP_HAS_CODES(HttpStatus.BAD_REQUEST, "COD_ERR_003", "그룹에 속한 코드가 있어 삭제할 수 없습니다"), PLAN_NOT_FOUND(HttpStatus.NOT_FOUND, "PLN_ERR_001", "주간 계획을 찾을 수 없습니다"); private final HttpStatus httpStatus; diff --git a/backend/src/main/java/com/srsystem/repository/CommonCodeGroupRepository.java b/backend/src/main/java/com/srsystem/repository/CommonCodeGroupRepository.java new file mode 100644 index 0000000..53d9fed --- /dev/null +++ b/backend/src/main/java/com/srsystem/repository/CommonCodeGroupRepository.java @@ -0,0 +1,12 @@ +package com.srsystem.repository; + +import com.srsystem.entity.CommonCodeGroup; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + +public interface CommonCodeGroupRepository extends JpaRepository { + Optional findByGroupCode(String groupCode); + List findAllByOrderBySortOrderAsc(); + boolean existsByGroupCode(String groupCode); +} diff --git a/backend/src/main/java/com/srsystem/repository/CommonCodeRepository.java b/backend/src/main/java/com/srsystem/repository/CommonCodeRepository.java index efa9e5e..57c2635 100644 --- a/backend/src/main/java/com/srsystem/repository/CommonCodeRepository.java +++ b/backend/src/main/java/com/srsystem/repository/CommonCodeRepository.java @@ -9,4 +9,5 @@ import java.util.List; @Repository public interface CommonCodeRepository extends JpaRepository { List findByGroupCodeAndIsActiveOrderBySortOrder(String groupCode, char isActive); + long countByGroupCode(String groupCode); } diff --git a/backend/src/main/java/com/srsystem/service/CommonCodeService.java b/backend/src/main/java/com/srsystem/service/CommonCodeService.java index 18bea86..4f9c683 100644 --- a/backend/src/main/java/com/srsystem/service/CommonCodeService.java +++ b/backend/src/main/java/com/srsystem/service/CommonCodeService.java @@ -2,8 +2,10 @@ package com.srsystem.service; import com.srsystem.dto.CommonCodeRequest; import com.srsystem.entity.CommonCode; +import com.srsystem.entity.CommonCodeGroup; import com.srsystem.exception.BusinessException; import com.srsystem.exception.ErrorCode; +import com.srsystem.repository.CommonCodeGroupRepository; import com.srsystem.repository.CommonCodeRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,6 +19,7 @@ import java.util.List; public class CommonCodeService { private final CommonCodeRepository commonCodeRepository; + private final CommonCodeGroupRepository commonCodeGroupRepository; public List getCodes(String groupCode) { if (groupCode != null) { @@ -73,4 +76,51 @@ public class CommonCodeService { code.setIsDeleted('Y'); commonCodeRepository.save(code); } + + // ---- Group CRUD ---- + + public List getGroups() { + return commonCodeGroupRepository.findAllByOrderBySortOrderAsc(); + } + + public CommonCodeGroup getGroup(String groupCode) { + return commonCodeGroupRepository.findByGroupCode(groupCode) + .orElseThrow(() -> new BusinessException(ErrorCode.CODE_NOT_FOUND)); + } + + @Transactional + public CommonCodeGroup createGroup(String groupCode, String groupName, String description, Integer sortOrder) { + if (commonCodeGroupRepository.existsByGroupCode(groupCode)) { + throw new BusinessException(ErrorCode.DUPLICATE_GROUP_CODE); + } + CommonCodeGroup group = CommonCodeGroup.builder() + .groupCode(groupCode) + .groupName(groupName) + .description(description) + .sortOrder(sortOrder != null ? sortOrder : 0) + .build(); + return commonCodeGroupRepository.save(group); + } + + @Transactional + public CommonCodeGroup updateGroup(String groupCode, String groupName, String description, Integer sortOrder) { + CommonCodeGroup group = commonCodeGroupRepository.findByGroupCode(groupCode) + .orElseThrow(() -> new BusinessException(ErrorCode.CODE_NOT_FOUND)); + if (groupName != null) group.setGroupName(groupName); + if (description != null) group.setDescription(description); + if (sortOrder != null) group.setSortOrder(sortOrder); + return commonCodeGroupRepository.save(group); + } + + @Transactional + public void deleteGroup(String groupCode) { + CommonCodeGroup group = commonCodeGroupRepository.findByGroupCode(groupCode) + .orElseThrow(() -> new BusinessException(ErrorCode.CODE_NOT_FOUND)); + long codeCount = commonCodeRepository.countByGroupCode(groupCode); + if (codeCount > 0) { + throw new BusinessException(ErrorCode.GROUP_HAS_CODES); + } + group.setIsDeleted('Y'); + commonCodeGroupRepository.save(group); + } } diff --git a/backend/src/main/resources/db/migration/V10__add_system_config.sql b/backend/src/main/resources/db/migration/V10__add_system_config.sql index 365e313..355d176 100644 --- a/backend/src/main/resources/db/migration/V10__add_system_config.sql +++ b/backend/src/main/resources/db/migration/V10__add_system_config.sql @@ -1,3 +1,5 @@ +CREATE SEQUENCE seq_system_config INCREMENT BY 50; + CREATE TABLE tb_system_config ( config_id BIGSERIAL PRIMARY KEY, config_key VARCHAR(100) NOT NULL UNIQUE, @@ -19,3 +21,5 @@ INSERT INTO tb_system_config (config_key, config_value, description) VALUES ('notification_slack_enabled', 'false', 'Slack 알림 활성화'), ('notification_email_enabled', 'false', 'Email 알림 활성화'), ('file_upload_max_size', '10485760', '파일 업로드 최대 크기 (bytes)'); + +CREATE SEQUENCE seq_system_config INCREMENT BY 50; \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V11__add_common_code_group.sql b/backend/src/main/resources/db/migration/V11__add_common_code_group.sql new file mode 100644 index 0000000..5fb145c --- /dev/null +++ b/backend/src/main/resources/db/migration/V11__add_common_code_group.sql @@ -0,0 +1,17 @@ +CREATE SEQUENCE sq_common_code_groups INCREMENT BY 50; + +CREATE TABLE tb_common_code_group ( + id BIGSERIAL PRIMARY KEY, + group_code VARCHAR(50) NOT NULL UNIQUE, + group_name VARCHAR(255) NOT NULL, + description VARCHAR(500), + sort_order INTEGER DEFAULT 0, + created_at TIMESTAMP, + updated_at TIMESTAMP, + is_deleted CHAR(1) DEFAULT 'N' +); + +INSERT INTO tb_common_code_group (group_code, group_name, sort_order) +SELECT DISTINCT group_code, group_code, 0 +FROM tb_common_code +WHERE is_deleted = 'N'; diff --git a/src/app/(main)/settings/codes/page.tsx b/src/app/(main)/settings/codes/page.tsx index 70ac743..f894f1f 100644 --- a/src/app/(main)/settings/codes/page.tsx +++ b/src/app/(main)/settings/codes/page.tsx @@ -1,10 +1,18 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import { Settings, Plus, Pencil, Trash2, X, Check } from 'lucide-react'; +import { Settings, Plus, Pencil, Trash2, X, Check, FolderTree, Hash, AlertCircle } from 'lucide-react'; import { useAuthStore } from '@/stores'; import apiClient from '@/services/apiClient'; +interface CommonCodeGroup { + id: number; + groupCode: string; + groupName: string; + description: string; + sortOrder: number; +} + interface CommonCode { codeId: number; groupCode: string; @@ -26,90 +34,239 @@ interface CommonCodeForm { isActive: 'Y' | 'N'; } +interface GroupForm { + groupCode: string; + groupName: string; + description: string; + sortOrder: number; +} + export default function CommonCodesPage() { const user = useAuthStore((s) => s.user); - const [groupCodes, setGroupCodes] = useState([]); - const [selectedGroup, setSelectedGroup] = useState(''); + + // Group state + const [groups, setGroups] = useState([]); + const [groupCounts, setGroupCounts] = useState>({}); + const [selectedGroup, setSelectedGroup] = useState(''); + const [groupsLoading, setGroupsLoading] = useState(true); + + // Code state const [codes, setCodes] = useState([]); - const [loading, setLoading] = useState(true); - const [showModal, setShowModal] = useState(false); - const [editItem, setEditItem] = useState(null); - const [form, setForm] = useState({ - groupCode: '', - codeValue: '', - codeName: '', - optionVal1: '', - optionVal2: '', - sortOrder: 1, - isActive: 'Y' + const [codesLoading, setCodesLoading] = useState(false); + + // Code modal state + const [showCodeModal, setShowCodeModal] = useState(false); + const [editCodeItem, setEditCodeItem] = useState(null); + const [codeForm, setCodeForm] = useState({ + groupCode: '', + codeValue: '', + codeName: '', + optionVal1: '', + optionVal2: '', + sortOrder: 1, + isActive: 'Y', }); + // Group modal state + const [showGroupModal, setShowGroupModal] = useState(false); + const [editGroupItem, setEditGroupItem] = useState(null); + const [groupForm, setGroupForm] = useState({ + groupCode: '', + groupName: '', + description: '', + sortOrder: 1, + }); + + // Error toast + const [errorToast, setErrorToast] = useState(null); + + // ── Load groups on mount ── useEffect(() => { - apiClient.get('/common-codes').then((res: any) => { + apiClient.get('/common-codes/groups').then((res: any) => { if (res.success) { - const groups = [...new Set(res.data.map((c: CommonCode) => c.groupCode))] as string[]; - setGroupCodes(groups); - if (groups.length > 0) setSelectedGroup(groups[0]); + const list: CommonCodeGroup[] = res.data; + setGroups(list); + // Derive code counts per group from all codes + apiClient.get('/common-codes').then((codeRes: any) => { + if (codeRes.success) { + const counts: Record = {}; + codeRes.data.forEach((c: CommonCode) => { + counts[c.groupCode] = (counts[c.groupCode] || 0) + 1; + }); + setGroupCounts(counts); + } + }).catch(() => {}); + if (list.length > 0) setSelectedGroup(list[0].groupCode); } - }).catch(() => {}).finally(() => setLoading(false)); + }).catch(() => {}).finally(() => setGroupsLoading(false)); }, []); + // ── Load codes when group selected ── useEffect(() => { if (!selectedGroup) return; - apiClient.get('/common-codes', { params: { groupCode: selectedGroup } }).then((res: any) => { - if (res.success) setCodes(res.data); - }).catch(() => {}); + setCodesLoading(true); + apiClient.get('/common-codes', { params: { groupCode: selectedGroup } }) + .then((res: any) => { + if (res.success) setCodes(res.data); + }) + .catch(() => {}) + .finally(() => setCodesLoading(false)); }, [selectedGroup]); - const openCreate = () => { - setEditItem(null); - setForm({ groupCode: selectedGroup, codeValue: '', codeName: '', optionVal1: '', optionVal2: '', sortOrder: 1, isActive: 'Y' }); - setShowModal(true); + // ── Auto-dismiss error toast ── + useEffect(() => { + if (errorToast) { + const t = setTimeout(() => setErrorToast(null), 4000); + return () => clearTimeout(t); + } + }, [errorToast]); + + // ── Group CRUD ── + const openCreateGroup = () => { + setEditGroupItem(null); + setGroupForm({ groupCode: '', groupName: '', description: '', sortOrder: (groups.length + 1) }); + setShowGroupModal(true); }; - const openEdit = (item: CommonCode) => { - setEditItem(item); - setForm({ - groupCode: item.groupCode, - codeValue: item.codeValue, - codeName: item.codeName, - optionVal1: item.optionVal1 || '', - optionVal2: item.optionVal2 || '', - sortOrder: item.sortOrder, - isActive: item.isActive as 'Y' | 'N' - }); - setShowModal(true); + const openEditGroup = (g: CommonCodeGroup) => { + setEditGroupItem(g); + setGroupForm({ groupCode: g.groupCode, groupName: g.groupName, description: g.description, sortOrder: g.sortOrder }); + setShowGroupModal(true); }; - const handleSave = async () => { + const handleSaveGroup = async () => { try { let res: any; - if (editItem) { - res = await apiClient.put(`/common-codes/${editItem.codeId}`, form); + if (editGroupItem) { + res = await apiClient.put(`/common-codes/groups/${editGroupItem.groupCode}`, { + groupName: groupForm.groupName, + description: groupForm.description, + sortOrder: groupForm.sortOrder, + }); } else { - res = await apiClient.post('/common-codes', form); + res = await apiClient.post('/common-codes/groups', groupForm); } - if (res?.success) { - setShowModal(false); + if (res?.success) { + setShowGroupModal(false); + const refreshRes: any = await apiClient.get('/common-codes/groups'); + if (refreshRes.success) { + setGroups(refreshRes.data); + // Refresh counts + const codeRes: any = await apiClient.get('/common-codes'); + if (codeRes.success) { + const counts: Record = {}; + codeRes.data.forEach((c: CommonCode) => { + counts[c.groupCode] = (counts[c.groupCode] || 0) + 1; + }); + setGroupCounts(counts); + } + } + } + } catch (e: any) { + setErrorToast(e?.response?.data?.message || 'Failed to save group'); + } + }; + + const handleDeleteGroup = async (g: CommonCodeGroup) => { + if (!confirm(`Delete group "${g.groupName}"? Only empty groups can be deleted.`)) return; + try { + await apiClient.delete(`/common-codes/groups/${g.groupCode}`); + const refreshRes: any = await apiClient.get('/common-codes/groups'); + if (refreshRes.success) { + setGroups(refreshRes.data); + if (selectedGroup === g.groupCode) { + setSelectedGroup(refreshRes.data.length > 0 ? refreshRes.data[0].groupCode : ''); + } + // Refresh counts + const codeRes: any = await apiClient.get('/common-codes'); + if (codeRes.success) { + const counts: Record = {}; + codeRes.data.forEach((c: CommonCode) => { + counts[c.groupCode] = (counts[c.groupCode] || 0) + 1; + }); + setGroupCounts(counts); + } + } + } catch (e: any) { + const msg = e?.response?.data?.message; + if (msg && msg.includes('code')) { + setErrorToast('Cannot delete group: it still contains codes. Remove all codes first.'); + } else { + setErrorToast(msg || 'Failed to delete group'); + } + } + }; + + // ── Code CRUD ── + const openCreateCode = () => { + setEditCodeItem(null); + setCodeForm({ groupCode: selectedGroup, codeValue: '', codeName: '', optionVal1: '', optionVal2: '', sortOrder: 1, isActive: 'Y' }); + setShowCodeModal(true); + }; + + const openEditCode = (item: CommonCode) => { + setEditCodeItem(item); + setCodeForm({ + groupCode: item.groupCode, + codeValue: item.codeValue, + codeName: item.codeName, + optionVal1: item.optionVal1 || '', + optionVal2: item.optionVal2 || '', + sortOrder: item.sortOrder, + isActive: item.isActive as 'Y' | 'N', + }); + setShowCodeModal(true); + }; + + const handleSaveCode = async () => { + try { + let res: any; + if (editCodeItem) { + res = await apiClient.put(`/common-codes/${editCodeItem.codeId}`, codeForm); + } else { + res = await apiClient.post('/common-codes', codeForm); + } + if (res?.success) { + setShowCodeModal(false); const refreshRes: any = await apiClient.get('/common-codes', { params: { groupCode: selectedGroup } }); if (refreshRes?.success) setCodes(refreshRes.data); + // Refresh group counts + const allRes: any = await apiClient.get('/common-codes'); + if (allRes.success) { + const counts: Record = {}; + allRes.data.forEach((c: CommonCode) => { + counts[c.groupCode] = (counts[c.groupCode] || 0) + 1; + }); + setGroupCounts(counts); + } } } catch (e) { console.error('Save error:', e); } }; - const handleDelete = async (id: number) => { + const handleDeleteCode = async (id: number) => { if (!confirm('Delete this code?')) return; try { await apiClient.delete(`/common-codes/${id}`); const refreshRes: any = await apiClient.get('/common-codes', { params: { groupCode: selectedGroup } }); if (refreshRes?.success) setCodes(refreshRes.data); + // Refresh group counts + const allRes: any = await apiClient.get('/common-codes'); + if (allRes.success) { + const counts: Record = {}; + allRes.data.forEach((c: CommonCode) => { + counts[c.groupCode] = (counts[c.groupCode] || 0) + 1; + }); + setGroupCounts(counts); + } } catch (e) { console.error('Delete error:', e); } }; + const selectedGroupMeta = groups.find(g => g.groupCode === selectedGroup); + if (user?.roleCode !== 'ADMIN') { return
Access denied. Admin only.
; } @@ -117,147 +274,305 @@ export default function CommonCodesPage() { return (
+ {/* Header */}

Common Codes

-

Manage system code tables

+

Manage code groups and system code tables

-
-
- {groupCodes.map(g => ( - - ))} +
+ )} + + {/* ── GROUP MANAGEMENT PANEL ── */} +
+
+

+ + Groups +

+ +
+ + {groupsLoading ? ( +
Loading groups...
+ ) : ( +
+ {groups.map(g => { + const count = groupCounts[g.groupCode] || 0; + const isSelected = selectedGroup === g.groupCode; + return ( +
setSelectedGroup(g.groupCode)} + className={`relative cursor-pointer rounded-2xl border p-4 transition-all hover:shadow-card ${ + isSelected + ? 'border-primary bg-primary/5 shadow-card' + : 'border-border bg-white hover:border-primary/30' + }`}> +
+
+
+ + {g.groupCode} +
+

{g.groupName}

+ {g.description && ( +

{g.description}

+ )} +
+
+ + +
+
+
+ + {count} code{count !== 1 ? 's' : ''} + + Order: {g.sortOrder} +
+
+ ); + })} +
+ )}
- {loading ? ( -
Loading...
- ) : ( -
- - - - - - - - - - - - - - {codes.map((c) => ( - - - - - - - - - - ))} - -
Code ValueCode NameOption1Option2OrderActiveActions
{c.codeValue}{c.codeName}{c.optionVal1 || '-'}{c.optionVal2 || '-'}{c.sortOrder} - {c.isActive === 'Y' ? ( - - ) : ( - - )} - - - -
- {codes.length === 0 && ( -
No codes found for this group.
+ {/* ── CODE TABLE ── */} + {selectedGroup && ( +
+
+
+

+ Codes: {selectedGroup} +

+ {selectedGroupMeta && ( + — {selectedGroupMeta.groupName} + )} +
+ +
+ + {codesLoading ? ( +
Loading codes...
+ ) : ( +
+ + + + + + + + + + + + + + {codes.map((c) => ( + + + + + + + + + + ))} + +
Code ValueCode NameOption1Option2OrderActiveActions
{c.codeValue}{c.codeName}{c.optionVal1 || '-'}{c.optionVal2 || '-'}{c.sortOrder} + {c.isActive === 'Y' ? ( + + ) : ( + + )} + + + +
+ {codes.length === 0 && ( +
No codes found for this group.
+ )} +
)}
)} - {showModal && ( -
setShowModal(false)}> -
setShowGroupModal(false)}> +
e.stopPropagation()}> -

{editItem ? 'Edit' : 'New'} Common Code

+

+ + {editGroupItem ? 'Edit Group' : 'New Group'} +

+ {!editGroupItem && ( +
+ + setGroupForm(f => ({ ...f, groupCode: e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, '') }))} + placeholder="e.g. TICKET_CATEGORY" + className="w-full px-3 py-2 border border-border rounded-lg text-sm font-mono" + /> +
+ )} + {editGroupItem && ( +
+ +
+ {groupForm.groupCode} +
+
+ )}
- - setForm(f => ({ ...f, groupCode: e.target.value }))} - className="w-full px-3 py-2 border border-border rounded-lg text-sm" + + setGroupForm(f => ({ ...f, groupName: e.target.value }))} + className="w-full px-3 py-2 border border-border rounded-lg text-sm" />
+
+ +