Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Service_Api/src/business/business.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export class BusinessService {
c.name AS customer_name,
p.name AS product_name,
p.version AS product_version,
pc.name AS product_category_name,
u.username AS manager_name,
CASE
WHEN ut.type = 'partner' THEN partner.id
Expand All @@ -158,6 +159,7 @@ export class BusinessService {
FROM business b
LEFT JOIN customer c ON b.customer_id = c.id
LEFT JOIN product p ON b.product_id = p.id
LEFT JOIN product_category pc ON p.category_id = pc.id
LEFT JOIN keycloak.USER_ENTITY u ON b.manager_id = u.id
LEFT JOIN user_company uc ON u.id = uc.user_id
LEFT JOIN user_type ut ON u.id = ut.user_id
Expand Down
10 changes: 9 additions & 1 deletion Service_Api/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export class UserService {
const whereConditions: string[] = [];
const params: any[] = [];

let orderByClause = `ORDER BY u.created_timestamp DESC`;

if (filters.username) {
whereConditions.push('u.username LIKE ?');
params.push(`%${filters.username}%`);
Expand Down Expand Up @@ -86,6 +88,12 @@ export class UserService {
params.push(...levelList);

whereConditions.push(`u.email IS NOT NULL AND u.email <> ''`);

orderByClause = `
ORDER BY
level ASC,
u.first_name COLLATE utf8_unicode_ci ASC
`;
}

const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
Expand Down Expand Up @@ -160,7 +168,7 @@ export class UserService {
LEFT JOIN licenses.${filters.type === 'partner' ? 'partner' : 'customer'} p
ON company_attr.value = CAST(p.id AS CHAR)
${whereClause}
ORDER BY u.created_timestamp DESC
${orderByClause}
LIMIT ? OFFSET ?
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export default function ProductDetailClient({ product, role, productId, prevPage
const [templates, setTemplateFiles] = useState<File[]>([]);
const [isTemplateLoading, setIsTemplateLoading] = useState(false);
const [templateError, setTemplateError] = useState<string | null>(null);
const [patch, setPatchFiles] = useState<File[]>([]);
const [isPatchLoading, setIsPatchLoading] = useState(false);
const [patchError, setPatchError] = useState<string | null>(null);

useEffect(() => {
if (value === 1 && !isReleaseLoading && product?.id) {
Expand All @@ -91,6 +94,9 @@ export default function ProductDetailClient({ product, role, productId, prevPage
if (value === 3 && !isTemplateLoading && templates.length === 0) {
fetchTemplate();
}
if (value === 4 && !isPatchLoading && patch.length === 0) {
fetchPatch();
}

}, [value]);

Expand Down Expand Up @@ -218,6 +224,22 @@ export default function ProductDetailClient({ product, role, productId, prevPage
}
};

const fetchPatch = async () => {
setIsPatchLoading(true);
setPatchError(null);
try {
const res = await fetch(`/api/product/${productId}/patch`);
if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
const data = await res.json();

setPatchFiles(data);
} catch (e: any) {
setPatchError(e.message);
} finally {
setIsPatchLoading(false);
}
};

const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
Expand Down Expand Up @@ -333,6 +355,7 @@ export default function ProductDetailClient({ product, role, productId, prevPage
<Tab label="릴리즈노트" {...tabProps(1)} />
<Tab label="AddOn" {...tabProps(2)} />
<Tab label="Template" {...tabProps(3)} />
<Tab label="Patch" {...tabProps(4)} />
</Tabs>
</Box>
<CustomTabPanel value={value} index={0}>
Expand Down Expand Up @@ -540,6 +563,56 @@ export default function ProductDetailClient({ product, role, productId, prevPage
</table>
)}
</CustomTabPanel>
<CustomTabPanel value={value} index={4}>
{isTemplateLoading ? (
<div className="flex justify-center items-center py-10 text-gray-500 text-sm">
로딩 중...
</div>
) : patchError ? (
<div className="flex justify-center items-center py-10 text-red-500 text-sm">
{patchError}
</div>
) : (
<table className="min-w-full divide-y divide-gray-200 border-b border-gray-100">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">이름</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">생성일</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">사이즈</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{patch.map((file) => (
<tr key={file.name} className="hover:bg-gray-50 cursor-pointer">
<td className="px-6 py-4 break-all">
<a href={`/api/product/${product?.id}/download?filePath=patch/${file.name}`} download rel="noopener noreferrer"
className="text-gray-900 hover:text-gray-500 transition-colors hover:underline">
{file.name}
</a>
{/* <a
href={file.path}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline text-sm font-medium"
>
{file.name}
</a> */}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{file.date}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{file.size}</td>
</tr>
))}
{patch.length === 0 && (
<tr>
<td colSpan={3} className="px-6 py-4 text-center text-gray-500 text-sm">
파일 정보가 없습니다.
</td>
</tr>
)}
</tbody>
</table>
)}
</CustomTabPanel>
</Box>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion Service_Gateway/src/app/(main)/user/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default async function UserPage({ searchParams: searchParamsPromise }: Us
<h1 className="text-2xl font-bold text-gray-800">사용자 관리</h1>
<Link
href="/user/register"
className={role === 'Admin' ? 'bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors' : 'hidden'}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors"
>
사용자 등록
</Link>
Expand Down
43 changes: 43 additions & 0 deletions Service_Gateway/src/app/api/product/[id]/patch/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { NextResponse } from 'next/server';
import { fetchWithAuth } from '@/utils/api';
import log from '@/utils/logger';
import { promises as fs } from 'fs';
import path from 'path';

/**
* patch 조회
* @param request
* @param params
* @returns
*/
export async function GET() {
try {
log.info('API URL ::: GET /product/patch');
const patchDir = path.join(process.cwd(), 'files/patch');
const fileNames = await fs.readdir(patchDir);

const files = await Promise.all(
fileNames.map(async (fileName) => {
const filePath = path.join(patchDir, fileName);
const stats = await fs.stat(filePath);

return {
name: fileName,
date: stats.mtime.toISOString(),
size: `${(stats.size / (1024 * 1024)).toFixed(2)} MB`,
path: filePath,
};
})
);

return new Response(JSON.stringify(files), {
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
log.info('API URL ::: GET /product/patch ERROR ::: '+error);
return new Response(
JSON.stringify({ error: 'Failed to fetch patch file list' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}
2 changes: 1 addition & 1 deletion Service_Gateway/src/app/api/user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function GET(request: Request) {
if (company) apiUrl.searchParams.set('company', company );
if (type) apiUrl.searchParams.set('type', type);
if (level) apiUrl.searchParams.set('level', level);
if (order) apiUrl.searchParams.set('level', order);
if (order) apiUrl.searchParams.set('order', order);
// 유저 역할에 따라 회사 정보 추가(파트너일 경우)
if (role) {
const data_userinfo = await userinfo();
Expand Down