Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feature/401Stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
TinyMurky committed Sep 20, 2024
2 parents 21aba48 + 15573b2 commit 388c52b
Show file tree
Hide file tree
Showing 15 changed files with 825 additions and 1 deletion.
10 changes: 10 additions & 0 deletions public/elements/cloud_upload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions public/elements/file_pdf.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/components/upload_certificate/certificate_item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

const CertificateItem = () => {
return (
<tr>
<td className="p-2">2024/01/01</td>
<td className="p-2">Invoice 000001</td>
<td className="p-2">59373022</td>
<td className="p-2">23. Proof of Return or Discount for ...</td>
<td className="p-2">Yes</td>
<td className="p-2">1,785,000 NTD</td>
<td className="p-2">Taxable 5%</td>
<td className="p-2">1,785,000 NTD</td>
<td className="p-2">20240417-001</td>
</tr>
);
};

export default CertificateItem;
55 changes: 55 additions & 0 deletions src/components/upload_certificate/certificate_table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import CertificateItem from '@/components/upload_certificate/certificate_item';
import Pagination from '@/components/upload_certificate/pagination'; // 引入 Pagination 組件

interface CertificateTableProps {
data: unknown[]; // Deprecated: (20240919 - tzuhan) will be replaced by actual data type
}

// Deprecated: (20240919 - tzuhan) will be replaced by actual data type
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const CertificateTable: React.FC<CertificateTableProps> = ({ data }) => {
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10; // Info: (20240919 - tzuhan) 每頁顯示的項目數
const totalItems = 100; // Info: (20240919 - tzuhan) 總項目數,實際情況中可以來自 API
const totalPages = Math.ceil(totalItems / itemsPerPage);

return (
<div className="rounded-lg bg-white p-4">
<div className="overflow-auto">
<table className="min-w-full bg-white">
<thead>
<tr>
<th className="p-2">Date</th>
<th className="p-2">Invoice Name/No.</th>
<th className="p-2">From / To-Tax ID</th>
<th className="p-2">Business Tax Format Code</th>
<th className="p-2">Deductible</th>
<th className="p-2">Price before tax</th>
<th className="p-2">Tax</th>
<th className="p-2">Total price</th>
<th className="p-2">Voucher No</th>
</tr>
</thead>
<tbody>
{/* Deprecated: (20240919 - tzuhan) Example of dynamic rows, should map actual data here */}
{[...Array(10)].map((_, index) => (
<CertificateItem key={`certificate-item-${index + 1}`} />
))}
</tbody>
</table>
</div>

{/* Info: (20240919 - tzuhan) 分頁組件 */}
<div className="mt-4 flex justify-center">
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={(page) => setCurrentPage(page)}
/>
</div>
</div>
);
};

export default CertificateTable;
202 changes: 202 additions & 0 deletions src/components/upload_certificate/filter_section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { IAPIName } from '@/interfaces/api_connection';
import APIHandler from '@/lib/utils/api_handler';
import React, { useState, useEffect, useCallback } from 'react';

interface FilterSectionProps {
apiName: IAPIName;
params?: Record<string, string | number | boolean>;
types?: string[];
statuses?: string[];
sortingOptions?: string[];
onApiResponse?: (data: unknown[]) => void; // Info: (20240919 - tzuhan) 回傳 API 回應資料
viewType: 'grid' | 'list';
viewToggleHandler: (viewType: 'grid' | 'list') => void;
}

const FilterSection: React.FC<FilterSectionProps> = ({
apiName,
params,
types = [],
statuses = [],
sortingOptions = [],
onApiResponse,
viewType,
viewToggleHandler,
}) => {
const [selectedType, setSelectedType] = useState<string | undefined>();
const [selectedStatus, setSelectedStatus] = useState<string | undefined>();
const [selectedDateRange, setSelectedDateRange] = useState<{ start?: string; end?: string }>({});
const [searchQuery, setSearchQuery] = useState<string | undefined>();
const [selectedSorting, setSelectedSorting] = useState<string | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(false);
const { trigger } = APIHandler<unknown[]>(apiName);

// Info: (20240919 - tzuhan) 發送 API 請求
const fetchData = useCallback(async () => {
try {
if (isLoading) return;
setIsLoading(true);
const { success, code, data } = await trigger({
params,
query: {
type: selectedType,
status: selectedStatus,
startDate: selectedDateRange.start,
endDate: selectedDateRange.end,
search: searchQuery,
sort: selectedSorting,
},
});
if (success && onApiResponse) onApiResponse(data!);
if (!success) {
// Deprecated: (20240919 - tzuhan) Debugging purpose only
// eslint-disable-next-line no-console
console.error('API Request Failed:', code);
}
} catch (error) {
// Deprecated: (20240919 - tzuhan) Debugging purpose only
// eslint-disable-next-line no-console
console.error('API Request error:', error);
} finally {
setIsLoading(false);
}
}, [isLoading, selectedType, selectedStatus, selectedDateRange, searchQuery, selectedSorting]);

const onSearchClick = () => {
setSearchQuery((document.getElementById('search') as HTMLInputElement)?.value);
};

// Info: (20240919 - tzuhan) 每次狀態變更時,組合查詢條件並發送 API 請求
useEffect(() => {
fetchData();
}, [selectedType, selectedStatus, selectedDateRange, searchQuery, selectedSorting]);

return (
<div
className="flex flex-wrap items-center justify-start space-x-4 rounded-lg bg-white p-4"
style={{ maxWidth: '100%' }}
>
{/* Info: (20240919 - tzuhan) 類型篩選 */}
{types.length > 0 && (
<div className="flex min-w-150px flex-col">
<label htmlFor="type" className="text-sm font-medium text-gray-500">
<div>Type</div>
<select
id="type"
className="rounded-md border border-gray-300 p-2"
onChange={(e) => setSelectedType(e.target.value)}
>
<option value={undefined}>All</option>
{types.map((type) => (
<option key={type} value={type}>
{type}
</option>
))}
</select>
</label>
</div>
)}

{/* Info: (20240919 - tzuhan) 狀態篩選 */}
{statuses.length > 0 && (
<div className="flex min-w-150px flex-col">
<label htmlFor="status" className="text-sm font-medium text-gray-500">
<div>Status</div>
<select
id="status"
className="rounded-md border border-gray-300 p-2"
onChange={(e) => setSelectedStatus(e.target.value)}
>
<option value={undefined}>All</option>
{statuses.map((status) => (
<option key={status} value={status}>
{status}
</option>
))}
</select>
</label>
</div>
)}

{/* Info: (20240919 - tzuhan) 時間區間篩選 */}
<div className="flex min-w-250px flex-col">
<label htmlFor="date-range" className="text-sm font-medium text-gray-500">
<div>Period</div>
<input
type="text"
id="date-range"
className="rounded-md border border-gray-300 p-2"
placeholder="Start Date - End Date"
onBlur={(e) => {
const [startDate, endDate] = e.target.value.split(' - ');
setSelectedDateRange({ start: startDate.trim(), end: endDate.trim() });
}}
/>
</label>
</div>

{/* Info: (20240919 - tzuhan) 搜索欄 */}
<div className="flex min-w-200px flex-col">
<label htmlFor="search" className="text-sm font-medium text-gray-500">
<div className="flex items-center">
<input
type="text"
id="search"
className="grow rounded-l-md border border-gray-300 p-2"
placeholder="Search"
onKeyDown={(e) => {
if (e.key === 'Enter') {
setSearchQuery(e.currentTarget.value);
}
}}
/>
<button type="button" className="rounded-r-md bg-gray-300 p-2" onClick={onSearchClick}>
🔍
</button>
</div>
</label>
</div>

{/* Info: (20240919 - tzuhan) 排序選項 */}
{sortingOptions.length > 0 && (
<div className="flex min-w-150px flex-col">
<label htmlFor="sort" className="text-sm font-medium text-gray-500">
{selectedSorting || 'Sort'}
</label>
<select
id="sort"
className="rounded-md border border-gray-300 p-2"
onChange={(e) => setSelectedSorting(e.target.value)}
>
<option value={undefined}>Default</option>
{sortingOptions.map((sort) => (
<option key={sort} value={sort}>
{sort}
</option>
))}
</select>
</div>
)}

{/* Info: (20240919 - tzuhan) 顯示風格切換 */}
<div className="flex min-w-150px items-center space-x-2">
<button
type="button"
className={`rounded border p-2 hover:bg-gray-300 ${viewType === 'grid' ? 'bg-gray-200' : ''}`}
onClick={() => viewToggleHandler('grid')}
>
Grid View
</button>
<button
type="button"
className={`rounded border p-2 hover:bg-gray-300 ${viewType === 'list' ? 'bg-gray-200' : ''}`}
onClick={() => viewToggleHandler('list')}
>
List View
</button>
</div>
</div>
);
};

export default FilterSection;
Loading

0 comments on commit 388c52b

Please sign in to comment.