com : app.vue - breadcrumb 주석

com : navbar.vue - 테마기능 삭제
func : VFlexTable 커스텀추가
fix : 계약관리, 결재함 스타일수정
This commit is contained in:
Kasi
2025-05-28 18:11:05 +09:00
parent 7340dc6299
commit d0c91e6a44
6 changed files with 694 additions and 142 deletions

View File

@@ -0,0 +1,595 @@
<script setup lang="ts">
import { type VNode } from 'vue'
import { flewTableWrapperSymbol } from '../base/VFlexTableWrapper.vue'
export interface VFlexTableColumn {
key: string
label: string
format: (value: any, row: any, index: number) => any
renderHeader?: () => VNode
renderRow?: (row: any, column: VFlexTableColumn, index: number) => VNode
align?: 'start' | 'center' | 'end'
bold?: boolean
inverted?: boolean
scrollX?: boolean
scrollY?: boolean
grow?: boolean | 'lg' | 'xl'
media?: boolean
cellClass?: string
}
export interface VFlexTableProps {
data?: any[]
columns?: Record<string, string | Partial<VFlexTableColumn>>
printObjects?: boolean
reactive?: boolean
compact?: boolean
rounded?: boolean
separators?: boolean
clickable?: boolean
subtable?: boolean
noHeader?: boolean
usePaymentHeader?: boolean
}
const emits = defineEmits<{
(e: 'rowClick', row: any, index: number): void
}>()
const props = withDefaults(defineProps<VFlexTableProps>(), {
columns: undefined,
usePaymentHeader: false,
data: () => [],
})
const wrapper = inject(flewTableWrapperSymbol, null)
const data = computed(() => {
if (wrapper?.data) return wrapper.data
if (props.reactive) {
if (isReactive(props.data)) {
return props.data
}
else {
return reactive(props.data)
}
}
return toRaw(props.data)
})
const defaultFormatter = (value: any) => value
const columns = computed(() => {
const columnsSrc = wrapper?.columns ?? props.columns
let columns: VFlexTableColumn[] = []
if (columnsSrc) {
for (const [key, label] of Object.entries(columnsSrc)) {
if (typeof label === 'string') {
columns.push({
format: defaultFormatter,
label,
key,
})
}
else {
columns.push({
format: defaultFormatter,
label: key,
key,
...(label as any),
})
}
}
}
else if (data.value.length > 0) {
for (const [key] of Object.entries(data.value[0])) {
columns.push({
format: defaultFormatter,
label: key,
key,
})
}
}
return columns
})
</script>
<template>
<div
class="flex-table"
:class="[
props.compact && 'is-compact',
props.rounded && 'is-rounded',
props.separators && 'with-separators',
props.noHeader && 'no-header',
props.clickable && 'is-table-clickable',
props.subtable && 'sub-table',
]"
>
<slot v-if="props.usePaymentHeader" name="payment-header">
<div
v-if="!props.noHeader"
class="flex-table-header"
>
<template
v-for="column in columns"
:key="'col' + column.key"
>
<slot
name="header-column"
:column="column"
>
<component
:is="{ render: column.renderHeader } as any"
v-if="column.renderHeader"
:class="[
column.grow === true && 'is-grow',
column.grow === 'lg' && 'is-grow-lg',
column.grow === 'xl' && 'is-grow-xl',
column.align === 'end' && 'cell-end',
column.align === 'center' && 'cell-center',
]"
/>
<span
v-else
:class="[
column.grow === true && 'is-grow',
column.grow === 'lg' && 'is-grow-lg',
column.grow === 'xl' && 'is-grow-xl',
column.align === 'end' && 'cell-end',
column.align === 'center' && 'cell-center',
]"
>{{ column.label }}</span>
</slot>
</template>
</div>
</slot>
<slot v-else name="header">
<div
v-if="!props.noHeader"
class="flex-table-header"
>
<template
v-for="column in columns"
:key="'col' + column.key"
>
<slot
name="header-column"
:column="column"
>
<component
:is="{ render: column.renderHeader } as any"
v-if="column.renderHeader"
:class="[
column.grow === true && 'is-grow',
column.grow === 'lg' && 'is-grow-lg',
column.grow === 'xl' && 'is-grow-xl',
column.align === 'end' && 'cell-end',
column.align === 'center' && 'cell-center',
]"
/>
<span
v-else
:class="[
column.grow === true && 'is-grow',
column.grow === 'lg' && 'is-grow-lg',
column.grow === 'xl' && 'is-grow-xl',
column.align === 'end' && 'cell-end',
column.align === 'center' && 'cell-center',
]"
>{{ column.label }}</span>
</slot>
</template>
</div>
</slot>
<slot name="body">
<template
v-for="(row, index) in data"
:key="index"
>
<slot
name="body-row-pre"
:row="row"
:columns="columns"
:index="index"
/>
<!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -->
<div
class="flex-table-item"
:class="[props.clickable && 'is-clickable']"
:tabindex="props.clickable ? 0 : undefined"
:role="props.clickable ? 'button' : undefined"
@keydown.enter.prevent="
() => {
props.clickable && emits('rowClick', row, index)
}
"
@click="
() => {
props.clickable && emits('rowClick', row, index)
}
"
>
<slot
name="body-row"
:row="row"
:columns="columns"
:index="index"
>
<template
v-for="column in columns"
:key="'row' + column.key"
>
<VFlexTableCell :column="column">
<slot
:name="`body-cell-${column.key}`"
:row="row"
:column="column"
:index="index"
:value="column.format(row[column.key], row, index)"
>
<component
:is="
{
render: () => column.renderRow?.(row, column, index),
} as any
"
v-if="column.renderRow"
/>
<span
v-else-if="
typeof column.format(row[column.key], row, index) === 'object'
"
:class="[
column.cellClass,
column.inverted && 'dark-inverted',
// !column.inverted && (column.bold ? 'dark-text' : 'light-text'),
]"
>
<details v-if="printObjects">
<div class="language-json py-4">
<pre><code>{{ column.format(row[column.key], row, index) }}</code></pre>
</div>
</details>
</span>
<span
v-else
:class="[
column.cellClass,
column.inverted && 'dark-inverted',
// !column.inverted && (column.bold ? 'dark-text' : 'light-text'),
]"
>
{{ column.format(row[column.key], row, index) }}
</span>
</slot>
</VFlexTableCell>
</template>
</slot>
</div>
<slot
name="body-row-post"
:row="row"
:columns="columns"
:index="index"
/>
</template>
</slot>
</div>
</template>
<style lang="scss">
.flex-table {
.flex-table-header {
display: flex;
align-items: center;
padding: 20px 10px;
background-color: var(--primary);
> span,
.text {
flex: 1 1 0;
display: flex;
align-items: center;
font-size: 1.0rem;
font-weight: 600;
color: var(--smoke-white);
text-transform: uppercase;
padding: 0 10px;
justify-content: center;
align-items: center;
display: flex;
&.is-checkbox {
display: flex;
justify-content: center;
align-items: center;
width: 30px;
max-width: 30px;
.checkbox {
padding: 0;
> span {
height: 22px;
}
}
}
&.cell-center {
justify-content: center;
}
&.cell-end {
justify-content: flex-end;
}
&.is-grow {
flex-grow: 2;
}
&.is-grow-lg {
flex-grow: 3;
}
&.is-grow-xl {
flex-grow: 6;
}
a {
color: var(--muted-grey);
}
}
.checkbox {
padding-bottom: 10px;
padding-top: 0;
> span {
min-height: 20px;
}
}
}
.flex-table-item {
display: flex;
align-items: stretch;
width: 100%;
min-height: 60px;
background: var(--white);
border: 1px solid color-mix(in oklab, var(--fade-grey), black 3%);
padding: 8px;
margin-bottom: 6px;
&.is-row {
border: none;
background: transparent;
}
}
&.sub-table {
.flex-table-item {
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
min-height: 40px;
border: none;
background: transparent;
.table-label {
font-family: var(--font);
text-transform: uppercase;
font-size: 0.8rem;
color: var(--light-text);
}
.table-total {
font-family: var(--font);
color: var(--dark-text);
font-weight: 500;
&.is-bigger {
font-size: 1.2rem;
font-weight: 600;
}
}
}
}
&.is-compact {
.flex-table-item {
margin-bottom: 0;
border-radius: 0;
&:not(:last-child) {
border-bottom: none;
}
}
&.is-rounded {
&:not(.no-header) {
.flex-table-item {
&:nth-of-type(2) {
border-radius: 8px 8px 0 0;
}
&:last-child {
margin-bottom: 6px;
border-radius: 0 0 8px 8px;
}
}
}
&.no-header {
.flex-table-item {
&:first-child {
border-radius: 8px 8px 0 0;
}
&:last-child {
margin-bottom: 6px;
border-radius: 0 0 8px 8px;
}
}
}
}
}
&:not(.is-compact) {
&.is-rounded {
.flex-table-item {
border-radius: 8px;
}
}
}
&.is-table-clickable {
.flex-table-item {
&:hover,
&:focus-within {
background: var(--widget-grey) !important;
}
}
}
&.with-separators {
.flex-table-item {
.flex-table-cell {
&:not(:first-of-type) {
border-inline-start: solid 1px color-mix(in oklab, var(--fade-grey), black 3%);
}
}
}
}
}
/* ==========================================================================
2. Flex Table Dark mode
========================================================================== */
.is-dark {
.flex-table {
&:not(.sub-table) {
.flex-table-item {
background: color-mix(in oklab, var(--dark-sidebar), white 6%);
border-color: color-mix(in oklab, var(--dark-sidebar), white 12%);
}
}
&.with-separators {
.flex-table-item {
.flex-table-cell {
&:not(:first-of-type) {
border-inline-start: dashed 1px color-mix(in oklab, var(--dark-sidebar), white 12%);
}
}
}
}
&.is-table-clickable {
.flex-table-item {
&:hover,
&:focus-within {
background: color-mix(in oklab, var(--dark-sidebar), white 12%) !important;
}
}
}
}
}
/* ==========================================================================
3. Media Queries
========================================================================== */
@media (width <= 767px) {
.flex-table {
.flex-table-header {
display: none;
}
.flex-table-item {
flex-direction: column;
justify-content: center;
width: 100% !important;
padding: 20px;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
> div {
border: none !important;
}
}
&:not(.sub-table) {
.flex-table-item {
.flex-table-cell {
> span,
> small,
> strong,
> p,
> div,
> .is-pushed-mobile,
> .text {
margin-inline-start: auto;
&.no-push {
margin-inline-start: 0 !important;
}
}
}
&:not(:first-child) {
.flex-table-cell {
&[data-th] {
&::before {
content: attr(data-th);
font-size: 0.9rem;
text-transform: uppercase;
font-weight: 500;
color: var(--muted-grey);
}
}
}
}
}
}
}
}
@media only screen and (width <= 767px) {
.flex-table {
&.sub-table {
padding-top: 16px;
.is-vhidden {
display: none !important;
}
.flex-table-item:not(.is-vhidden) {
flex-direction: revert !important;
}
}
}
}
.paymentColumn1 {
min-width: 20%;
}
.paymentColumn2 {
min-width: 40%;
}
.paymentColumn3 {
min-width: 10%;
}
.paymentColumn4 {
min-width: 20%;
}
.paymentColumn5 {
min-width: 10%;
}
</style>

View File

@@ -62,11 +62,6 @@ const links = ref<NavbarItem[]>([
</template> </template>
<template #toolbar> <template #toolbar>
<!-- <LayoutSwitcher class="is-hidden-mobile" />-->
<!-- <ToolbarNotification />-->
<!-- <ToolbarActivity />-->
<ToolbarThemeToggle />
<!-- <ToolbarLanguage />-->
<ToolbarUserProfile right class="ml-2 is-hidden-mobile" /> <ToolbarUserProfile right class="ml-2 is-hidden-mobile" />
</template> </template>

View File

@@ -7,24 +7,24 @@ definePage({
requiresAuth: true, requiresAuth: true,
}, },
}) })
const breadcrumb = [ // const breadcrumb = [
{ // {
label: '가격조사', // label: '가격조사',
hideLabel: true, // hideLabel: true,
// use external links // // use external links
link: 'https://vuero.cssninja.io/', // link: 'https://vuero.cssninja.io/',
}, // },
{ // {
label: '가격조사', // label: '가격조사',
// or generate a router link with 'to' props // // or generate a router link with 'to' props
to: '/components/', // to: '/components/',
} // }
] // ]
</script> </script>
<template> <template>
<Navbar> <Navbar>
<VBreadcrumb :items="breadcrumb" separator="succeeds" /> <!-- <VBreadcrumb :items="breadcrumb" separator="succeeds" />-->
<RouterView /> <RouterView />
</Navbar> </Navbar>
</template> </template>

View File

@@ -4,6 +4,9 @@ import type { VFlexTableWrapperSortFunction, VFlexTableWrapperFilterFunction } f
import { users } from '/src/data/layouts/card-grid-v1' import { users } from '/src/data/layouts/card-grid-v1'
import PriceDetail from "/@src/pages/app/priceDetail.vue"; import PriceDetail from "/@src/pages/app/priceDetail.vue";
import {conveterNo, updateApprovalStatus} from "/@src/service/approvalApi.ts"; import {conveterNo, updateApprovalStatus} from "/@src/service/approvalApi.ts";
import {formatDatefromString} from "/@src/utils/common/comfunc.ts";
import {getPriceList} from "/@src/service/priceApi.ts";
import {getContractList} from "/@src/service/contractApi.ts";
onBeforeMount(async () => { onBeforeMount(async () => {
@@ -13,6 +16,45 @@ const isModalOpen = ref(false)
const selectedRow = ref<any>(null) const selectedRow = ref<any>(null)
const prcsNo = ref<string>('') const prcsNo = ref<string>('')
const router = useRouter() const router = useRouter()
const totalItems = ref(0) // 전체 아이템 수
const currentPage = ref<number>(1) // 현재 페이지
const itemsPerPage = 8
const totalPages = ref(1)
async function getApprovalListData(){
const today = new Date()
searchParamsList.regSdt = new Date().setDate(today.getDate() - 30)
searchParamsList.regEdt = new Date().setDate(today.getDate())
const priceBase = {
params:{
regSdt: formatDatefromString(searchParamsList.regSdt),
regEdt: formatDatefromString(searchParamsList.regEdt),
page: 1,
row: itemsPerPage
}
}
const result = await getPriceList(priceBase)
params.priceData = result.content
//페이지 관련 값 설정
totalItems.value = result.totalElements
totalPages.value = result.totalPages
}
const searchApprovalDoc = async (item) => {
const searchParams = {
params: {
title : searchParamsList.title, //제목
regSdt: formatDatefromString(searchParamsList.regSdt),//등록시작일
regEdt: formatDatefromString(searchParamsList.regEdt),//등록종료일
page: item,//페이지
row: itemsPerPage //아이템갯수
}
}
const result = await getContractList(searchParams)
params.priceData = result.content
totalItems.value = result.totalElements
}
const masks = ref({ const masks = ref({
modelValue: 'YYYY-MM-DD', modelValue: 'YYYY-MM-DD',
@@ -34,14 +76,7 @@ const params = reactive({
}, },
{ key: 'process', label: '구분', cellClass: 'paymentColumn5' }, { key: 'process', label: '구분', cellClass: 'paymentColumn5' },
], ],
flexWrapperColumn: { approvalParams: [],
apprNo: { label: '결재번호', cellClass: 'paymentColumn1', searchable: true, sortable: true },
title: { label: '제목', cellClass: 'paymentColumn2', searchable: true, sortable: true },
name: { label: '작성자', cellClass: 'paymentColumn3', searchable: true, sortable: true },
regDt: { label: '등록일', cellClass: 'paymentColumn4', searchable: true, sortable: true },
process: { label: '구분', cellClass: 'paymentColumn5', searchable: true, sortable: true },
},
paymentParams: [],
rowData: [], rowData: [],
}) })
@@ -57,10 +92,13 @@ async function getIntegratedPaymentList() {
sabun: '17131303', // 김진형 17131303, 손원창 17131304 sabun: '17131303', // 김진형 17131303, 손원창 17131304
} }
const result = await getIntegratedApproval(paymentParams) const result = await getIntegratedApproval(paymentParams)
params.paymentParams = result.content.map(item => ({ params.approvalParams = result.content.map(item => ({
...item, ...item,
process: gubunMap[item.gubun] || '', process: gubunMap[item.gubun] || '',
})) }))
//페이지 관련 값 설정
totalItems.value = result.totalElements
totalPages.value = result.totalPages
} }
async function updateIntegratedPaymentApprovalFunc() { async function updateIntegratedPaymentApprovalFunc() {
@@ -95,16 +133,16 @@ const gubunMap = {
} }
const searchApproval = async () => { const searchApproval = async () => {
const paymentParams = { const searchParams = {
params: { params: {
title: params.title, title: params.title,
page: '1', page: '1',
row: '10', row: '10',
}, },
} }
const result = await getIntegratedApproval(paymentParams) const result = await getIntegratedApproval(searchParams)
params.paymentParams = result.content params.approvalParams = result.content
console.log(params.paymentParams) console.log(params.approvalParams)
} }
type User = (typeof users)[0] type User = (typeof users)[0]
@@ -271,7 +309,7 @@ const onRowClick2 = (row: any) => {
</div> </div>
<div class="datatable-wrapper"> <div class="datatable-wrapper">
<ComVFlexTable <ComVFlexTable
:data="params.paymentParams" :data="params.approvalParams"
:columns="params.flexColumn" :columns="params.flexColumn"
:separators="true" :separators="true"
:clickable="true" :clickable="true"
@@ -308,119 +346,32 @@ const onRowClick2 = (row: any) => {
<VButton type="submit" color="primary" raised @click="updateIntegratedPaymentApprovalFunc">승인</VButton> <VButton type="submit" color="primary" raised @click="updateIntegratedPaymentApprovalFunc">승인</VButton>
</template> </template>
</VModal> </VModal>
<!-- <VFlexTableWrapper :columns="params.flexWrapperColumn" :data="params.paymentParams">-->
<!--&lt;!&ndash;-->
<!-- Here we retrieve the internal wrapperState.-->
<!-- Note that we can not destructure it-->
<!-- &ndash;&gt;-->
<!-- <template #default="wrapperState">-->
<!-- <pre>data: {{ data }}</pre>-->
<!-- &lt;!&ndash; We can place any content inside the default slot&ndash;&gt;-->
<!-- <VFlexTableToolbar>-->
<!-- <template #left>-->
<!-- &lt;!&ndash; We can bind wrapperState.searchInput to any input &ndash;&gt;-->
<!-- <VField>-->
<!-- <VControl icon="lucide:search">-->
<!-- <input-->
<!-- v-model="wrapperState.searchInput"-->
<!-- type="text"-->
<!-- class="input is-rounded"-->
<!-- placeholder="Filter..."-->
<!-- >-->
<!-- </VControl>-->
<!-- </VField>-->
<!-- </template>-->
<!-- <template #right>--> <VFlexPagination
<!-- &lt;!&ndash; We can also bind wrapperState.limit &ndash;&gt;--> :item-per-page="itemsPerPage"
<!-- <VField>--> :total-items="totalItems"
<!-- <VControl>--> v-model:current-page="currentPage"
<!-- <div class="select is-rounded">--> />
<!-- <select v-model="wrapperState.limit">-->
<!-- <option :value="1">-->
<!-- 1 results per page-->
<!-- </option>-->
<!-- <option :value="10">-->
<!-- 10 results per page-->
<!-- </option>-->
<!-- <option :value="15">-->
<!-- 15 results per page-->
<!-- </option>-->
<!-- <option :value="25">-->
<!-- 25 results per page-->
<!-- </option>-->
<!-- <option :value="50">-->
<!-- 50 results per page-->
<!-- </option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </VControl>-->
<!-- </VField>-->
<!-- </template>-->
<!-- </VFlexTableToolbar>-->
<!-- &lt;!&ndash;-->
<!-- The VFlexTable "data" and "columns" props-->
<!-- will be inherited from parent VFlexTableWrapper-->
<!-- &ndash;&gt;-->
<!-- <VFlexTable rounded>-->
<!-- &lt;!&ndash; Custom "name" cell content &ndash;&gt;-->
<!-- <template #body-cell="{ row, column }">-->
<!-- <template v-if="column.key === 'name'">-->
<!-- <VAvatar-->
<!-- size="medium"-->
<!-- :picture="row.medias.avatar"-->
<!-- :badge="row.medias.badge"-->
<!-- :initials="row.initials"-->
<!-- />-->
<!-- <div>-->
<!-- <span class="dark-text" :title="row.name">-->
<!-- {{ row?.shortname }}-->
<!-- </span>-->
<!-- <VTextEllipsis-->
<!-- width="280px"-->
<!-- class="light-text"-->
<!-- :title="row.bio"-->
<!-- >-->
<!-- <small>{{ row?.bio }}</small>-->
<!-- </VTextEllipsis>-->
<!-- </div>-->
<!-- </template>-->
<!-- </template>-->
<!-- </VFlexTable>-->
<!-- &lt;!&ndash; Table Pagination with wrapperState.page binded&ndash;&gt;-->
<!-- <VFlexPagination-->
<!-- v-model:current-page="wrapperState.page"-->
<!-- class="mt-6"-->
<!-- :item-per-page="wrapperState.limit"-->
<!-- :total-items="wrapperState.total"-->
<!-- :max-links-displayed="5"-->
<!-- no-router-->
<!-- />-->
<!-- </template>-->
<!-- </VFlexTableWrapper>-->
</div> </div>
</div> </div>
<!-- <VButtons class="v-buttons-right-align">-->
<!-- <VButton-->
<!-- color="primary"-->
<!-- icon="fas fa-plus"-->
<!-- elevated-->
<!-- to="/app/PriceInsert"-->
<!-- >-->
<!-- 등록-->
<!-- </VButton>-->
<!-- </VButtons>-->
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.datatable-toolbar {
padding: 1rem 1.5rem;
margin: 0 0 2rem 0;
}
.flex-table .flex-table-item {
min-height: 3rem;
}
input[readonly] { input[readonly] {
background-color: #f5f5f5; /* 살짝 회색 배경 */ background-color: #f5f5f5;
color: #888; /* 글자색 연하게 */ color: #888;
cursor: not-allowed; /* 마우스 커서 변경 */ cursor: not-allowed;
border-color: #e0e0e0; /* 테두리 연하게 */ border-color: #e0e0e0;
} }
.v-buttons-right-align { .v-buttons-right-align {

View File

@@ -207,3 +207,15 @@ function getContractDetail(){
</VButtons> </VButtons>
</div> </div>
</template> </template>
<style scoped lang="scss">
.datatable-toolbar {
padding: 1rem 1.5rem;
margin: 0 0 2rem 0;
}
.flex-table .flex-table-item {
min-height: 3rem;
}
</style>

View File

@@ -326,7 +326,7 @@ const onPrcsFileDownload = async (prcsNo: string, fileOrd: number, logiFnm: stri
<td>견적서확인</td> <td>견적서확인</td>
<td colspan="9"> <td colspan="9">
<slot name="status" <slot name="status"
v-if="!params.svyYn" v-if="params.stCd == '0100'"
> >
<div style="display: flex; justify-content: flex-end; gap: 8px;"> <div style="display: flex; justify-content: flex-end; gap: 8px;">
<VButton <VButton
@@ -340,7 +340,7 @@ const onPrcsFileDownload = async (prcsNo: string, fileOrd: number, logiFnm: stri
</div> </div>
</slot> </slot>
<div class="mt-2"> <div class="mt-2">
<ComVFlexTable <VFlexTableCustomize
:key="params.prcsBizs.length" :key="params.prcsBizs.length"
:data="params.prcsBizs" :data="params.prcsBizs"
:columns="params.prcsBizsColumn" :columns="params.prcsBizsColumn"
@@ -373,13 +373,12 @@ const onPrcsFileDownload = async (prcsNo: string, fileOrd: number, logiFnm: stri
</template> </template>
<!-- 나머지 컬럼만 슬롯 사용 --> <!-- 나머지 컬럼만 슬롯 사용 -->
<template #body-cell="{ column, index, value, row }"> <template #body-cell="{ column, index, value, row }">
<pre>{{ row.sendYn }}</pre>
<div> <div>
<span v-if="column.key=='num'">{{index + 1}}</span> <span v-if="column.key=='num'">{{index + 1}}</span>
<span v-else>{{ value }}</span> <span v-else>{{ value }}</span>
</div> </div>
</template> </template>
</ComVFlexTable> </VFlexTableCustomize>
</div> </div>
</td> </td>
</tr> </tr>