Merge branch 'feat/recording-export' into 'development'

[FEAT/FE] Recording

See merge request mbugroup/lti-web-client!381
This commit is contained in:
Rivaldi A N S
2026-04-09 07:24:57 +00:00
4 changed files with 90 additions and 0 deletions
@@ -48,6 +48,7 @@ import { useUiStore } from '@/stores/ui/ui.store';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import ButtonFilter from '@/components/helper/ButtonFilter'; import ButtonFilter from '@/components/helper/ButtonFilter';
import Dropdown from '@/components/Dropdown';
// ===== STATUS BADGE UTILITIES ===== // ===== STATUS BADGE UTILITIES =====
const statusTextMap: Record<string, string> = { const statusTextMap: Record<string, string> = {
@@ -352,6 +353,9 @@ const RecordingTable = () => {
const [isRejectLoading, setIsRejectLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false);
const [, setApprovalNotes] = useState(''); const [, setApprovalNotes] = useState('');
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
const singleDeleteModal = useModal(); const singleDeleteModal = useModal();
const approveModal = useModal(); const approveModal = useModal();
const rejectModal = useModal(); const rejectModal = useModal();
@@ -686,6 +690,14 @@ const RecordingTable = () => {
}); });
}, [selectedRowIds, recordings, isRecordingApproved]); }, [selectedRowIds, recordings, isRecordingApproved]);
const exportToExcelHandler = async () => {
setIsLoadingExportingToExcel(true);
await RecordingApi.exportToExcel(getTableFilterQueryString());
setIsLoadingExportingToExcel(false);
};
useEffect(() => { useEffect(() => {
if (isResponseSuccess(recordings) && recordings.data) { if (isResponseSuccess(recordings) && recordings.data) {
const newSelection: Record<string, boolean> = {}; const newSelection: Record<string, boolean> = {};
@@ -1313,6 +1325,50 @@ const RecordingTable = () => {
onClick={handleFilterModalOpen} onClick={handleFilterModalOpen}
className='px-3 py-2.5' className='px-3 py-2.5'
/> />
<Dropdown
align='end'
direction='bottom'
trigger={
<Button
variant='outline'
color='none'
className={cn(
'px-3 py-2.5 rounded-lg font-semibold text-sm gap-1.5',
'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft'
)}
>
<Icon
width={20}
height={20}
icon='heroicons:cloud-arrow-down'
/>
Export
<div className='w-6.5 h-5 flex items-center justify-center border-l border-base-content/10'>
<Icon
width={14}
height={14}
icon='heroicons:chevron-down'
/>
</div>
</Button>
}
className={{
content:
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
}}
>
<Button
variant='ghost'
color='none'
onClick={exportToExcelHandler}
isLoading={isLoadingExportingToExcel}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Export to Excel
</Button>
</Dropdown>
</div> </div>
</div> </div>
+26
View File
@@ -12,6 +12,8 @@ import {
NextDayRecording, NextDayRecording,
} from '@/types/api/production/recording'; } from '@/types/api/production/recording';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { httpClient } from '@/services/http/client';
import { formatDate } from '@/lib/helper';
export const ProjectFlockKandangApi = new BaseApiService< export const ProjectFlockKandangApi = new BaseApiService<
ProjectFlockKandang, ProjectFlockKandang,
@@ -88,6 +90,30 @@ export class RecordingService extends BaseApiService<
} }
); );
} }
async exportToExcel(initialQueryString: string) {
const params = new URLSearchParams(initialQueryString);
params.set('export', 'excel');
const queryString = `?${params.toString()}`;
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
method: 'GET',
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([res]));
const link = document.createElement('a');
link.href = url;
const fileName = `recording-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
link.remove();
}
} }
export const RecordingApi = new RecordingService('/production/recordings'); export const RecordingApi = new RecordingService('/production/recordings');
+7
View File
@@ -9,6 +9,13 @@ export type RequestOptions<B = unknown> = {
auth?: AuthMode; // 'cookie' | 'bearer' | 'none' auth?: AuthMode; // 'cookie' | 'bearer' | 'none'
token?: string; // required if auth === 'bearer' token?: string; // required if auth === 'bearer'
timeoutMs?: number; timeoutMs?: number;
responseType?:
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text'
| 'stream';
}; };
export class HttpError extends Error { export class HttpError extends Error {
+1
View File
@@ -40,6 +40,7 @@ export async function httpClient<T, B = unknown>(
data: opts.body, data: opts.body,
timeout: opts.timeoutMs ?? 10_000, timeout: opts.timeoutMs ?? 10_000,
withCredentials: isCookieAuth && !isBearerAuth, withCredentials: isCookieAuth && !isBearerAuth,
responseType: opts.responseType,
headers: { headers: {
...(isFormData ? {} : { 'Content-Type': 'application/json' }), ...(isFormData ? {} : { 'Content-Type': 'application/json' }),
...(opts.headers ?? {}), ...(opts.headers ?? {}),