summaryrefslogtreecommitdiff
path: root/lib/tags
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-17 10:50:28 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-17 10:50:28 +0000
commitfb276ed3db86fe4fc0c0fcd870fd3d085b034be0 (patch)
tree4a8ab1027d7fd14602a0f837d4e18b04e2169e58 /lib/tags
parent4eb7532f822c821fb6b69bf103bd075fefba769b (diff)
(대표님) 벤더데이터 S-EDP 변경사항 대응(seperator), 정기평가 점수오류, dim 준비
Diffstat (limited to 'lib/tags')
-rw-r--r--lib/tags/service.ts181
-rw-r--r--lib/tags/table/add-tag-dialog.tsx199
2 files changed, 275 insertions, 105 deletions
diff --git a/lib/tags/service.ts b/lib/tags/service.ts
index e65ab65b..a1dff137 100644
--- a/lib/tags/service.ts
+++ b/lib/tags/service.ts
@@ -12,6 +12,8 @@ import { countTags, insertTag, selectTags } from "./repository";
import { getErrorMessage } from "../handle-error";
import { getFormMappingsByTagType } from './form-mapping-service';
import { contractItems, contracts } from "@/db/schema/contract";
+import { getCodeListsByID } from "../sedp/sync-object-class";
+import { projects } from "@/db/schema";
// 폼 결과를 위한 인터페이스 정의
@@ -1261,52 +1263,69 @@ export interface ClassOption {
* Class 옵션 목록을 가져오는 함수
* 이제 각 클래스는 연결된 tagTypeCode와 tagTypeDescription을 포함
*/
-export async function getClassOptions(packageId?: number) {
- if (!packageId) {
- throw new Error("패키지 ID가 필요합니다");
- }
+export async function getClassOptions(selectedPackageId: number): Promise<UpdatedClassOption[]> {
+ try {
+ // 1. 먼저 contractItems에서 projectId 조회
+ const packageInfo = await db
+ .select({
+ projectId: contracts.projectId
+ })
+ .from(contractItems)
+ .innerJoin(contracts, eq(contractItems.contractId, contracts.id))
+ .where(eq(contractItems.id, selectedPackageId))
+ .limit(1);
- // First, get the projectId from the contract associated with the package
- const packageInfo = await db
- .select({
- projectId: contracts.projectId
- })
- .from(contractItems)
- .innerJoin(contracts, eq(contracts.id, contractItems.contractId))
- .where(eq(contractItems.id, packageId))
- .limit(1);
+ if (packageInfo.length === 0) {
+ throw new Error(`Contract item with ID ${selectedPackageId} not found`);
+ }
- if (!packageInfo.length) {
- throw new Error("패키지를 찾을 수 없거나 연결된 프로젝트가 없습니다");
- }
+ const projectId = packageInfo[0].projectId;
- const projectId = packageInfo[0].projectId;
+ // 2. 태그 클래스들을 서브클래스 정보와 함께 조회
+ const tagClassesWithSubclasses = await db
+ .select({
+ id: tagClasses.id,
+ code: tagClasses.code,
+ label: tagClasses.label,
+ tagTypeCode: tagClasses.tagTypeCode,
+ subclasses: tagClasses.subclasses,
+ subclassRemark: tagClasses.subclassRemark,
+ })
+ .from(tagClasses)
+ .where(eq(tagClasses.projectId, projectId))
+ .orderBy(tagClasses.code);
+ // 3. 태그 타입 정보도 함께 조회 (description을 위해)
+ const tagTypesMap = new Map();
+ const tagTypesList = await db
+ .select({
+ code: tagTypes.code,
+ description: tagTypes.description,
+ })
+ .from(tagTypes)
+ .where(eq(tagTypes.projectId, projectId));
- // Now get the tag classes filtered by projectId
- const rows = await db
- .select({
- id: tagClasses.id,
- code: tagClasses.code,
- label: tagClasses.label,
- tagTypeCode: tagClasses.tagTypeCode,
- tagTypeDescription: tagTypes.description,
- })
- .from(tagClasses)
- .leftJoin(tagTypes, and(
- eq(tagTypes.code, tagClasses.tagTypeCode),
- eq(tagTypes.projectId, tagClasses.projectId)
- ))
- .where(eq(tagClasses.projectId, projectId));
-
- console.log(rows)
-
- return rows.map((row) => ({
- code: row.code,
- label: row.label,
- tagTypeCode: row.tagTypeCode,
- tagTypeDescription: row.tagTypeDescription ?? "",
- }));
+ tagTypesList.forEach(tagType => {
+ tagTypesMap.set(tagType.code, tagType.description);
+ });
+
+ // 4. 클래스 옵션으로 변환
+ const classOptions: UpdatedClassOption[] = tagClassesWithSubclasses.map(cls => ({
+ value: cls.code,
+ label: cls.label,
+ code: cls.code,
+ description: cls.label,
+ tagTypeCode: cls.tagTypeCode,
+ tagTypeDescription: tagTypesMap.get(cls.tagTypeCode) || cls.tagTypeCode,
+ subclasses: cls.subclasses || [],
+ subclassRemark: cls.subclassRemark || {},
+ }));
+
+ return classOptions;
+ } catch (error) {
+ console.error("Error fetching class options with subclasses:", error);
+ throw new Error("Failed to fetch class options");
+ }
}
interface SubFieldDef {
name: string
@@ -1317,7 +1336,12 @@ interface SubFieldDef {
delimiter: string | null
}
-export async function getSubfieldsByTagType(tagTypeCode: string, selectedPackageId: number) {
+export async function getSubfieldsByTagType(
+ tagTypeCode: string,
+ selectedPackageId: number,
+ subclassRemark: string = "",
+ subclass: string = "",
+) {
try {
// 1. 먼저 contractItems에서 projectId 조회
const packageInfo = await db
@@ -1352,9 +1376,10 @@ export async function getSubfieldsByTagType(tagTypeCode: string, selectedPackage
for (const sf of rows) {
// projectId가 필요한 경우 getSubfieldType과 getSubfieldOptions 함수에도 전달
const subfieldType = await getSubfieldType(sf.attributesId, projectId);
+ const subclassMatched =subclassRemark.includes(sf.attributesId ) ? subclass: null
const subfieldOptions = subfieldType === "select"
- ? await getSubfieldOptions(sf.attributesId, projectId)
+ ? await getSubfieldOptions(sf.attributesId, projectId, subclassMatched) // subclassRemark 파라미터 추가
: [];
formattedSubFields.push({
@@ -1375,6 +1400,7 @@ export async function getSubfieldsByTagType(tagTypeCode: string, selectedPackage
}
+
async function getSubfieldType(attributesId: string, projectId: number): Promise<"select" | "text"> {
const optRows = await db
.select()
@@ -1403,9 +1429,58 @@ export interface SubfieldOption {
/**
* SubField의 옵션 목록을 가져오는 보조 함수
*/
-async function getSubfieldOptions(attributesId: string, projectId: number): Promise<SubfieldOption[]> {
+async function getSubfieldOptions(
+ attributesId: string,
+ projectId: number,
+ subclass: string = ""
+): Promise<SubfieldOption[]> {
try {
- const rows = await db
+ // 1. subclassRemark가 있는 경우 API에서 코드 리스트 가져와서 필터링
+ if (subclass && subclass.trim() !== "") {
+ // 프로젝트 코드를 projectId로부터 조회
+ const projectInfo = await db
+ .select({
+ code: projects.code
+ })
+ .from(projects)
+ .where(eq(projects.id, projectId))
+ .limit(1);
+
+ if (projectInfo.length === 0) {
+ throw new Error(`Project with ID ${projectId} not found`);
+ }
+
+ const projectCode = projectInfo[0].code;
+
+ // API에서 코드 리스트 가져오기
+ const codeListValues = await getCodeListsByID(projectCode);
+
+ // 서브클래스 리마크 값들을 분리 (쉼표, 공백 등으로 구분)
+ const remarkValues = subclass
+ .split(/[,\s]+/) // 쉼표나 공백으로 분리
+ .map(val => val.trim())
+ .filter(val => val.length > 0);
+
+ if (remarkValues.length > 0) {
+ // REMARK 필드가 remarkValues 중 하나를 포함하고 있는 항목들 필터링
+ const filteredCodeValues = codeListValues.filter(codeValue =>
+ remarkValues.some(remarkValue =>
+ // 대소문자 구분 없이 포함 여부 확인
+ codeValue.VALUE.toLowerCase().includes(remarkValue.toLowerCase()) ||
+ remarkValue.toLowerCase().includes(codeValue.VALUE.toLowerCase())
+ )
+ );
+
+ // 필터링된 결과를 PRNT_VALUE -> value, DESC -> label로 변환
+ return filteredCodeValues.map((codeValue) => ({
+ value: codeValue.PRNT_VALUE,
+ label: codeValue.DESC
+ }));
+ }
+ }
+
+ // 2. subclassRemark가 없는 경우 기존 방식으로 DB에서 조회
+ const allOptions = await db
.select({
code: tagSubfieldOptions.code,
label: tagSubfieldOptions.label
@@ -1416,18 +1491,24 @@ async function getSubfieldOptions(attributesId: string, projectId: number): Prom
eq(tagSubfieldOptions.attributesId, attributesId),
eq(tagSubfieldOptions.projectId, projectId),
)
- )
+ );
- return rows.map((row) => ({
+ return allOptions.map((row) => ({
value: row.code,
label: row.label
- }))
+ }));
} catch (error) {
- console.error(`Error fetching options for attribute ${attributesId}:`, error)
- return []
+ console.error(`Error fetching filtered options for attribute ${attributesId}:`, error);
+ return [];
}
}
+export interface UpdatedClassOption extends ClassOption {
+ tagTypeCode: string
+ tagTypeDescription?: string
+ subclasses: {id: string, desc: string}[]
+ subclassRemark: Record<string, string>
+}
/**
* Tag Type 목록을 가져오는 함수
diff --git a/lib/tags/table/add-tag-dialog.tsx b/lib/tags/table/add-tag-dialog.tsx
index cb71896c..10167c62 100644
--- a/lib/tags/table/add-tag-dialog.tsx
+++ b/lib/tags/table/add-tag-dialog.tsx
@@ -62,10 +62,11 @@ import {
TagTypeOption,
} from "@/lib/tags/service"
-// Updated to support multiple rows
+// Updated to support multiple rows and subclass
interface MultiTagFormValues {
class: string;
tagType: string;
+ subclass: string; // 새로 추가된 서브클래스 필드
rows: Array<{
[key: string]: string;
tagNo: string;
@@ -83,10 +84,15 @@ interface SubFieldDef {
delimiter?: string
}
-// 클래스 옵션 인터페이스
+// 업데이트된 클래스 옵션 인터페이스 (서브클래스 정보 포함)
interface UpdatedClassOption extends ClassOption {
tagTypeCode: string
tagTypeDescription?: string
+ subclasses: {
+ id: string;
+ desc: string;
+ }[] // 서브클래스 배열 추가
+ subclassRemark: Record<string, string> // 서브클래스 리마크 추가
}
interface AddTagDialogProps {
@@ -99,6 +105,8 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) {
const [open, setOpen] = React.useState(false)
const [tagTypeList, setTagTypeList] = React.useState<TagTypeOption[]>([])
const [selectedTagTypeCode, setSelectedTagTypeCode] = React.useState<string | null>(null)
+ const [selectedClassOption, setSelectedClassOption] = React.useState<UpdatedClassOption | null>(null)
+ const [selectedSubclass, setSelectedSubclass] = React.useState<string>("")
const [subFields, setSubFields] = React.useState<SubFieldDef[]>([])
const [classOptions, setClassOptions] = React.useState<UpdatedClassOption[]>([])
const [classSearchTerm, setClassSearchTerm] = React.useState("")
@@ -112,31 +120,29 @@ export function AddTagDialog({ selectedPackageId }: AddTagDialogProps) {
const fieldIdsRef = React.useRef<Record<string, string>>({})
const classOptionIdsRef = React.useRef<Record<string, string>>({})
-
console.log(selectedPackageId, "tag")
// ---------------
- // Load Class Options
+ // Load Class Options (서브클래스 정보 포함)
// ---------------
-// In the AddTagDialog component
-React.useEffect(() => {
- const loadClassOptions = async () => {
- setIsLoadingClasses(true)
- try {
- // Pass selectedPackageId to the function
- const result = await getClassOptions(selectedPackageId)
- setClassOptions(result)
- } catch (err) {
- toast.error("클래스 옵션을 불러오는데 실패했습니다.")
- } finally {
- setIsLoadingClasses(false)
+ React.useEffect(() => {
+ const loadClassOptions = async () => {
+ setIsLoadingClasses(true)
+ try {
+ // getClassOptions 함수가 서브클래스 정보도 포함하도록 수정되었다고 가정
+ const result = await getClassOptions(selectedPackageId)
+ setClassOptions(result)
+ } catch (err) {
+ toast.error("클래스 옵션을 불러오는데 실패했습니다.")
+ } finally {
+ setIsLoadingClasses(false)
+ }
}
- }
- if (open) {
- loadClassOptions()
- }
-}, [open, selectedPackageId]) // Add selectedPackageId to the dependency array
+ if (open) {
+ loadClassOptions()
+ }
+ }, [open, selectedPackageId])
// ---------------
// react-hook-form with fieldArray support for multiple rows
@@ -145,6 +151,7 @@ React.useEffect(() => {
defaultValues: {
tagType: "",
class: "",
+ subclass: "", // 서브클래스 필드 추가
rows: [{
tagNo: "",
description: ""
@@ -158,12 +165,13 @@ React.useEffect(() => {
})
// ---------------
- // Load subfields by TagType code
+ // 서브클래스별로 필터링된 서브필드 로드
// ---------------
- async function loadSubFieldsByTagTypeCode(tagTypeCode: string) {
+ async function loadFilteredSubFieldsByTagTypeCode(tagTypeCode: string, subclassRemark: string, subclass: string) {
setIsLoadingSubFields(true)
try {
- const { subFields: apiSubFields } = await getSubfieldsByTagType(tagTypeCode, selectedPackageId)
+ // 수정된 getSubfieldsByTagType 함수 호출 (subclassRemark 파라미터 추가)
+ const { subFields: apiSubFields } = await getSubfieldsByTagType(tagTypeCode, selectedPackageId, subclassRemark ,subclass )
const formattedSubFields: SubFieldDef[] = apiSubFields.map(field => ({
name: field.name,
label: field.label,
@@ -202,6 +210,10 @@ React.useEffect(() => {
// ---------------
async function handleSelectClass(classOption: UpdatedClassOption) {
form.setValue("class", classOption.label)
+ form.setValue("subclass", "") // 서브클래스 초기화
+ setSelectedClassOption(classOption)
+ setSelectedSubclass("")
+
if (classOption.tagTypeCode) {
setSelectedTagTypeCode(classOption.tagTypeCode)
// If you have tagTypeList, you can find the label
@@ -211,49 +223,76 @@ React.useEffect(() => {
} else if (classOption.tagTypeDescription) {
form.setValue("tagType", classOption.tagTypeDescription)
}
- await loadSubFieldsByTagTypeCode(classOption.tagTypeCode)
+
+ // 서브클래스가 있으면 서브필드 로딩을 하지 않고 대기
+ if (classOption.subclasses && classOption.subclasses.length > 0) {
+ setSubFields([]) // 서브클래스 선택을 기다림
+ } else {
+ // 서브클래스가 없으면 바로 서브필드 로딩
+ await loadFilteredSubFieldsByTagTypeCode(classOption.tagTypeCode, "","")
+ }
}
}
// ---------------
+ // Handle subclass selection
+ // ---------------
+ async function handleSelectSubclass(subclassCode: string) {
+ if (!selectedClassOption || !selectedTagTypeCode) return
+
+ setSelectedSubclass(subclassCode)
+ form.setValue("subclass", subclassCode)
+
+ // 선택된 서브클래스의 리마크 값 가져오기
+ const subclassRemarkValue = selectedClassOption.subclassRemark[subclassCode] || ""
+
+ // 리마크 값으로 필터링된 서브필드 로드
+ await loadFilteredSubFieldsByTagTypeCode(selectedTagTypeCode, subclassRemarkValue, subclassCode)
+ }
+
+ // ---------------
// Build TagNo from subfields automatically for each row
// ---------------
React.useEffect(() => {
if (subFields.length === 0) {
return;
}
-
+
const subscription = form.watch((value) => {
if (!value.rows || subFields.length === 0) {
return;
}
-
+
const rows = [...value.rows];
rows.forEach((row, rowIndex) => {
if (!row) return;
-
+
let combined = "";
subFields.forEach((sf, idx) => {
const fieldValue = row[sf.name] || "";
- combined += fieldValue;
- if (fieldValue && idx < subFields.length - 1 && sf.delimiter) {
+
+ // delimiter를 앞에 붙이기 (첫 번째 필드가 아니고, 현재 필드에 값이 있고, delimiter가 있는 경우)
+ if (idx > 0 && fieldValue && sf.delimiter) {
combined += sf.delimiter;
}
+
+ combined += fieldValue;
});
-
+
const currentTagNo = form.getValues(`rows.${rowIndex}.tagNo`);
if (currentTagNo !== combined) {
form.setValue(`rows.${rowIndex}.tagNo`, combined, {
- shouldDirty: true, // Changed from false to true
- shouldTouch: true, // Changed from false to true
- shouldValidate: true, // Changed from false to true
+ shouldDirty: true,
+ shouldTouch: true,
+ shouldValidate: true,
});
}
});
});
-
+
return () => subscription.unsubscribe();
}, [subFields, form]);
+
// ---------------
// Check if tag numbers are valid
// ---------------
@@ -263,9 +302,10 @@ React.useEffect(() => {
const tagNo = row.tagNo;
return tagNo && tagNo.trim() !== "" && !tagNo.includes("??");
});
- }, [form.watch()]); // Watch the entire form to catch all changes
+ }, [form.watch()]);
+
// ---------------
- // Submit handler for multiple tags
+ // Submit handler for multiple tags (서브클래스 정보 포함)
// ---------------
async function onSubmit(data: MultiTagFormValues) {
if (!selectedPackageId) {
@@ -280,10 +320,11 @@ React.useEffect(() => {
// Process each row
for (const row of data.rows) {
- // Create tag data from the row and shared class/tagType
+ // Create tag data from the row and shared class/tagType/subclass
const tagData: CreateTagSchema = {
tagType: data.tagType,
class: data.class,
+ // subclass: data.subclass, // 서브클래스 정보 추가
tagNo: row.tagNo,
description: row.description,
...Object.fromEntries(
@@ -316,7 +357,6 @@ React.useEffect(() => {
if (failedTags.length > 0) {
console.log("Failed tags:", failedTags);
-
toast.error(`${failedTags.length}개의 태그 생성에 실패했습니다.`);
}
@@ -339,11 +379,10 @@ React.useEffect(() => {
// Add a new row
// ---------------
function addRow() {
- // Create a properly typed row with index signature to allow dynamic properties
const newRow: {
tagNo: string;
description: string;
- [key: string]: string; // This allows any string key with string values
+ [key: string]: string;
} = {
tagNo: "",
description: ""
@@ -355,8 +394,6 @@ React.useEffect(() => {
});
append(newRow);
-
- // Force form validation after row is added
setTimeout(() => form.trigger(), 0);
}
@@ -365,18 +402,14 @@ React.useEffect(() => {
// ---------------
function duplicateRow(index: number) {
const rowToDuplicate = form.getValues(`rows.${index}`);
- // Use proper typing with index signature
const newRow: {
tagNo: string;
description: string;
[key: string]: string;
} = { ...rowToDuplicate };
- // Clear the tagNo field as it will be auto-generated
newRow.tagNo = "";
append(newRow);
-
- // Force form validation after row is duplicated
setTimeout(() => form.trigger(), 0);
}
@@ -400,7 +433,7 @@ React.useEffect(() => {
)
return (
- <FormItem className="w-1/2">
+ <FormItem className="w-1/3">
<FormLabel>Class</FormLabel>
<FormControl>
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
@@ -482,6 +515,45 @@ React.useEffect(() => {
}
// ---------------
+ // Render Subclass field (새로 추가)
+ // ---------------
+ function renderSubclassField(field: any) {
+ const hasSubclasses = selectedClassOption?.subclasses && selectedClassOption.subclasses.length > 0
+
+ if (!hasSubclasses) {
+ return null
+ }
+
+ return (
+ <FormItem className="w-1/3">
+ <FormLabel>Subclass</FormLabel>
+ <FormControl>
+ <Select
+ value={field.value || ""}
+ onValueChange={(value) => {
+ field.onChange(value)
+ handleSelectSubclass(value)
+ }}
+ disabled={!selectedClassOption}
+ >
+ <SelectTrigger className="h-9">
+ <SelectValue placeholder="서브클래스 선택..." />
+ </SelectTrigger>
+ <SelectContent>
+ {selectedClassOption?.subclasses.map((subclass) => (
+ <SelectItem key={subclass.id} value={subclass.id}>
+ {subclass.desc}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )
+ }
+
+ // ---------------
// Render TagType field (readonly after class selection)
// ---------------
function renderTagTypeField(field: any) {
@@ -494,8 +566,10 @@ React.useEffect(() => {
[isReadOnly]
)
+ const width = selectedClassOption?.subclasses && selectedClassOption.subclasses.length > 0 ? "w-1/3" : "w-2/3"
+
return (
- <FormItem className="w-1/2">
+ <FormItem className={width}>
<FormLabel>Tag Type</FormLabel>
<FormControl>
{isReadOnly ? (
@@ -536,9 +610,13 @@ React.useEffect(() => {
}
if (subFields.length === 0 && selectedTagTypeCode) {
+ const message = selectedClassOption?.subclasses && selectedClassOption.subclasses.length > 0
+ ? "서브클래스를 선택해주세요."
+ : "이 태그 유형에 대한 필드가 없습니다."
+
return (
<div className="py-4 text-center text-muted-foreground">
- 이 태그 유형에 대한 필드가 없습니다.
+ {message}
</div>
)
}
@@ -713,9 +791,7 @@ React.useEffect(() => {
/>
)}
</FormControl>
- {/* <FormMessage>{sf.expression}</FormMessage> */}
</FormItem>
-
)}
/>
</TableCell>
@@ -779,7 +855,7 @@ React.useEffect(() => {
variant="outline"
className="w-full border-dashed"
onClick={addRow}
- disabled={!selectedTagTypeCode || isLoadingSubFields}
+ disabled={!selectedTagTypeCode || isLoadingSubFields || subFields.length === 0}
>
<Plus className="h-4 w-4 mr-2" />
새 행 추가
@@ -808,9 +884,12 @@ React.useEffect(() => {
form.reset({
tagType: "",
class: "",
+ subclass: "",
rows: [{ tagNo: "", description: "" }]
});
setSelectedTagTypeCode(null);
+ setSelectedClassOption(null);
+ setSelectedSubclass("");
setSubFields([]);
}
setOpen(o);
@@ -826,7 +905,7 @@ React.useEffect(() => {
<DialogHeader>
<DialogTitle>새 태그 추가</DialogTitle>
<DialogDescription>
- 클래스를 선택하여 태그 유형과 하위 필드를 로드한 다음, 여러 행을 추가하여 여러 태그를 생성하세요.
+ 클래스와 서브클래스를 선택하여 태그 유형과 하위 필드를 로드한 다음, 여러 행을 추가하여 여러 태그를 생성하세요.
</DialogDescription>
</DialogHeader>
@@ -835,7 +914,7 @@ React.useEffect(() => {
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6"
>
- {/* 클래스 및 태그 유형 선택 */}
+ {/* 클래스, 서브클래스, 태그 유형 선택 */}
<div className="flex gap-4">
<FormField
key="class-field"
@@ -845,6 +924,13 @@ React.useEffect(() => {
/>
<FormField
+ key="subclass-field"
+ control={form.control}
+ name="subclass"
+ render={({ field }) => renderSubclassField(field)}
+ />
+
+ <FormField
key="tag-type-field"
control={form.control}
name="tagType"
@@ -865,11 +951,14 @@ React.useEffect(() => {
form.reset({
tagType: "",
class: "",
+ subclass: "",
rows: [{ tagNo: "", description: "" }]
});
setOpen(false);
setSubFields([]);
setSelectedTagTypeCode(null);
+ setSelectedClassOption(null);
+ setSelectedSubclass("");
}}
disabled={isSubmitting}
>