revamp template
This commit is contained in:
22
src/pages/pengajuan_kredit/list/blocks/ChatMessageIn.tsx
Normal file
22
src/pages/pengajuan_kredit/list/blocks/ChatMessageIn.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
|
||||
interface IChatMessageInProps {
|
||||
text: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
const ChatMessageIn = ({ text, time }: IChatMessageInProps) => {
|
||||
return (
|
||||
<div className="flex items-end gap-3.5 px-5">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div
|
||||
className="card shadow-none flex flex-col bg-gray-100 gap-2.5 p-3 rounded-bl-none text-2sm font-medium text-gray-700"
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
/>
|
||||
<span className="text-2xs font-medium text-gray-500">{time}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatMessageIn, type IChatMessageInProps };
|
||||
27
src/pages/pengajuan_kredit/list/blocks/ChatMessageOut.tsx
Normal file
27
src/pages/pengajuan_kredit/list/blocks/ChatMessageOut.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
import { KeenIcon } from '@/components';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface IChatMessageOutProps {
|
||||
text: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
const ChatMessageOut = ({ text, time }: IChatMessageOutProps) => {
|
||||
return (
|
||||
<div className="flex items-end justify-end gap-3.5 px-5">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div
|
||||
className="card shadow-none flex bg-primary text-primary-inverse text-2sm font-medium flex-col gap-2.5 p-3 rounded-be-none"
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end relative">
|
||||
<span className="text-2xs font-medium text-gray-600 me-6">{time}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatMessageOut, type IChatMessageOutProps };
|
||||
58
src/pages/pengajuan_kredit/list/blocks/DatePicker.tsx
Normal file
58
src/pages/pengajuan_kredit/list/blocks/DatePicker.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { format } from 'date-fns';
|
||||
import { KeenIcon } from '@/components/keenicons';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface DatePickerProps {
|
||||
date?: Date;
|
||||
setDate: (date: Date) => void;
|
||||
value?: string;
|
||||
onChange?: (e: { target: { value: string } }) => void;
|
||||
className?: string; // Tambahkan className sebagai props
|
||||
}
|
||||
|
||||
const DatePicker = ({
|
||||
date = new Date(),
|
||||
setDate,
|
||||
value,
|
||||
onChange,
|
||||
className
|
||||
}: DatePickerProps) => {
|
||||
const handleDateSelect = (selectedDate: Date | undefined) => {
|
||||
if (selectedDate) {
|
||||
setDate(selectedDate);
|
||||
if (onChange) {
|
||||
onChange({ target: { value: format(selectedDate, 'yyyy-MM-dd') } });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'btn btn-sm btn-light data-[state=open]:bg-light-active w-full',
|
||||
!date && 'text-gray-400',
|
||||
className // Terapkan className di sini
|
||||
)}
|
||||
>
|
||||
<KeenIcon icon="calendar" className="me-0.5 mb-0.5 text-info" />
|
||||
{value || (date ? format(date, 'yyyy-MM-dd') : 'Pilih Tanggal')}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={handleDateSelect}
|
||||
defaultMonth={date}
|
||||
numberOfMonths={1}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export { DatePicker };
|
||||
60
src/pages/pengajuan_kredit/list/blocks/DateRangePicker.tsx
Normal file
60
src/pages/pengajuan_kredit/list/blocks/DateRangePicker.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { DateRange } from 'react-day-picker';
|
||||
import { format, subDays } from 'date-fns';
|
||||
import { KeenIcon } from '@/components/keenicons';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface DateRangePickerProps {
|
||||
date: DateRange | undefined;
|
||||
setDate: (date: DateRange | undefined) => void;
|
||||
}
|
||||
|
||||
const DateRangePicker = ({ date, setDate }: DateRangePickerProps) => {
|
||||
useEffect(() => {
|
||||
if (!date) {
|
||||
const today = new Date();
|
||||
const last30Days = subDays(today, 30);
|
||||
setDate({ from: last30Days, to: today });
|
||||
}
|
||||
}, [date, setDate]);
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
id="date"
|
||||
className={cn(
|
||||
'btn btn-sm btn-light data-[state=open]:bg-light-active w-full',
|
||||
!date && 'text-gray-400'
|
||||
)}
|
||||
>
|
||||
<KeenIcon icon="calendar" className="me-0.5" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, 'LLL dd, y')} - {format(date.to, 'LLL dd, y')}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, 'LLL dd, y')
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date range</span>
|
||||
)}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="end">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export { DateRangePicker };
|
||||
293
src/pages/pengajuan_kredit/list/blocks/ListToolBar.tsx
Normal file
293
src/pages/pengajuan_kredit/list/blocks/ListToolBar.tsx
Normal file
@ -0,0 +1,293 @@
|
||||
import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components';
|
||||
import { useCreditListContext } from '../hooks';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DateRangePicker } from './DateRangePicker';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { DateRange } from 'react-day-picker';
|
||||
import XlsIcon from '@/public_media/file-types/xls.svg';
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
|
||||
const ListToolBar = ({ setFilter }: any) => {
|
||||
const navigate = useNavigate();
|
||||
const { table, reload } = useDataGrid();
|
||||
const { date, setDate, doExportData } = useCreditListContext();
|
||||
const [filteredDate, setFilteredDate] = useState<DateRange | null>(null);
|
||||
const [selectedStatus, setSelectedStatus] = useState<string[]>([]);
|
||||
const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState(false);
|
||||
|
||||
const [debitorName, setDebitorName] = useState('');
|
||||
const [company, setCompany] = useState('');
|
||||
const [code, setCode] = useState('');
|
||||
const [employeeId, setEmployeeId] = useState('');
|
||||
|
||||
const handleReload = () => {
|
||||
reload();
|
||||
};
|
||||
|
||||
const handleFilterData = () => {
|
||||
try {
|
||||
const filters = [];
|
||||
if (selectedStatus.length > 0) {
|
||||
filters.push({
|
||||
id: 'a.status',
|
||||
value: selectedStatus
|
||||
});
|
||||
}
|
||||
|
||||
if (debitorName != '') filters.push({ id: 'debtor.name', value: `%${debitorName}%` });
|
||||
if (company != '') filters.push({ id: 'company.name', value: `%${company}%` });
|
||||
if (employeeId != '') filters.push({ id: 'debtor.employee_id', value: `%${employeeId}%` });
|
||||
|
||||
if (code != '') {
|
||||
filters.push({ id: 'a.code', value: code });
|
||||
}
|
||||
|
||||
if (date?.from && date?.to) {
|
||||
setFilteredDate({
|
||||
from: date.from,
|
||||
to: date.to
|
||||
});
|
||||
|
||||
filters.push({
|
||||
id: 'application_date_from',
|
||||
value: date.from.toISOString().split('T')[0]
|
||||
});
|
||||
filters.push({
|
||||
id: 'application_date_to',
|
||||
value: date.to.toISOString().split('T')[0]
|
||||
});
|
||||
|
||||
table.setColumnFilters(filters);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Error filter data');
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetData = () => {
|
||||
setDate({
|
||||
from: new Date(new Date().setDate(new Date().getDate() - 31)),
|
||||
to: new Date()
|
||||
});
|
||||
setFilteredDate(null);
|
||||
setSelectedStatus([]);
|
||||
setCode('');
|
||||
table.setColumnFilters([]);
|
||||
reload();
|
||||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
navigate('/pengajuan_kredit/list/add');
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
const sorting = table.getState().sorting;
|
||||
doExportData(sorting, table.getState().columnFilters);
|
||||
};
|
||||
|
||||
const handleStatusChange = (statusId: string) => {
|
||||
console.log('statusId :', statusId);
|
||||
setSelectedStatus((prev) =>
|
||||
prev.includes(statusId) ? prev.filter((id) => id != statusId) : [...prev, statusId]
|
||||
);
|
||||
};
|
||||
|
||||
const handleStatusDropdownOpen = () => {
|
||||
setIsStatusDropdownOpen(true);
|
||||
};
|
||||
|
||||
const handleStatusDropdownClose = () => {
|
||||
setIsStatusDropdownOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
handleStatusDropdownClose();
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [handleStatusDropdownClose]);
|
||||
|
||||
return (
|
||||
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
|
||||
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
|
||||
<div className="flex justify-between w-full items-center">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-auto min-w-[220px]">
|
||||
<DateRangePicker date={date} setDate={setDate} />
|
||||
</div>
|
||||
<div className="w-auto min-w-[150px]">
|
||||
<DropdownMenu open={isStatusDropdownOpen} onOpenChange={handleStatusDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="h-7.5 text-[0.8rem] w-full justify-between">
|
||||
{selectedStatus.length > 0
|
||||
? `Select Status: ${selectedStatus.length}`
|
||||
: 'Select Status'}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuCheckboxItem
|
||||
key="open"
|
||||
checked={selectedStatus.includes('open')}
|
||||
onCheckedChange={() => handleStatusChange('open')}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="justify-between h-6 text-[0.8rem]"
|
||||
>
|
||||
OPEN
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
key="under_review"
|
||||
checked={selectedStatus.includes('under_review')}
|
||||
onCheckedChange={() => handleStatusChange('under_review')}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="justify-between h-6 text-[0.8rem]"
|
||||
>
|
||||
UNDER REVIEW
|
||||
</DropdownMenuCheckboxItem>
|
||||
{/* <DropdownMenuCheckboxItem
|
||||
key="revision"
|
||||
checked={selectedStatus.includes('revision')}
|
||||
onCheckedChange={() => handleStatusChange('revision')}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="justify-between h-6 text-[0.8rem]"
|
||||
>
|
||||
REVISON
|
||||
</DropdownMenuCheckboxItem> */}
|
||||
<DropdownMenuCheckboxItem
|
||||
key="approved"
|
||||
checked={selectedStatus.includes('approved')}
|
||||
onCheckedChange={() => handleStatusChange('approved')}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="justify-between h-6 text-[0.8rem]"
|
||||
>
|
||||
APPROVE
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuCheckboxItem
|
||||
key="rejected"
|
||||
checked={selectedStatus.includes('rejected')}
|
||||
onCheckedChange={() => handleStatusChange('rejected')}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="justify-between h-6 text-[0.8rem]"
|
||||
>
|
||||
REJECT
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="w-auto min-w-[100px]">
|
||||
<label className="input input-sm">
|
||||
<KeenIcon icon="filter" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Code"
|
||||
value={code}
|
||||
onChange={(event) => setCode(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-auto min-w-[100px]">
|
||||
<label className="input input-sm">
|
||||
<KeenIcon icon="filter" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Company"
|
||||
value={company}
|
||||
onChange={(event) => setCompany(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-auto min-w-[100px]">
|
||||
<label className="input input-sm">
|
||||
<KeenIcon icon="filter" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Debitor Name"
|
||||
value={debitorName}
|
||||
onChange={(event) => setDebitorName(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-auto min-w-[100px]">
|
||||
<label className="input input-sm">
|
||||
<KeenIcon icon="filter" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Employee ID"
|
||||
value={employeeId}
|
||||
onChange={(event) => setEmployeeId(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex item-center gap-3 ms-2 me-10">
|
||||
<DefaultTooltip title={'Filter'} placement={'top'}>
|
||||
<Button variant="outline" className="h-7.5" onClick={handleFilterData}>
|
||||
<KeenIcon icon="filter" />
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
<DefaultTooltip title={'Reset Filter'} placement={'top'}>
|
||||
<Button variant="outline" className="h-7.5" onClick={handleResetData}>
|
||||
<KeenIcon icon="arrow-circle-left" />
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3 items-center">
|
||||
{/* <Button
|
||||
variant="outline"
|
||||
className="h-7.5 text-[0.8rem]"
|
||||
onClick={() => handleCreateNew()}
|
||||
>
|
||||
Add Data
|
||||
</Button> */}
|
||||
<DefaultTooltip title={'Export Data'} placement={'top'}>
|
||||
<Button variant={'outline'} className="h-7.5 min-w-[58px]" onClick={handleExport}>
|
||||
<img src={toAbsoluteUrl('/media/file-types/xls.svg')} className="" alt="" />
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
<DefaultTooltip title={'Refresh'} placement={'top'}>
|
||||
<Button variant="outline" className="h-7.5" onClick={handleReload}>
|
||||
<KeenIcon icon="arrows-circle" />
|
||||
</Button>
|
||||
</DefaultTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ListToolBar };
|
||||
49
src/pages/pengajuan_kredit/list/blocks/Notes.tsx
Normal file
49
src/pages/pengajuan_kredit/list/blocks/Notes.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { toAbsoluteUrl } from '@/utils';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface IDropdownNotificationsItemProps {
|
||||
userName: string;
|
||||
avatar: string;
|
||||
description: string;
|
||||
time: string;
|
||||
text: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
const Notes = ({
|
||||
userName,
|
||||
avatar,
|
||||
description,
|
||||
time,
|
||||
text,
|
||||
company
|
||||
}: IDropdownNotificationsItemProps) => {
|
||||
const [emailInput, setEmailInput] = useState('');
|
||||
return (
|
||||
<div className="flex grow gap-2.5">
|
||||
<div className="relative shrink-0 mt-0.5">
|
||||
<img className="h-[20px] max-w-none" src={toAbsoluteUrl(avatar)} alt="logo" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-2sm font-medium flex justify-between">
|
||||
<p className="text-gray-900 font-semibold">{userName}</p>
|
||||
<span className="text-gray-700"> {description} </span>
|
||||
<span className="flex items-center text-2xs font-medium text-gray-500">{time}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="card shadow-none flex flex-col gap-2.5 p-3.5 rounded-lg bg-light-active"
|
||||
style={{ borderColor: company == 'BRI' ? '#02529c' : '' }}
|
||||
>
|
||||
<div className="text-2sm font-semibold text-gray-600 mb-px">
|
||||
<span className="text-gray-700 font-medium"> {text} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Notes };
|
||||
6
src/pages/pengajuan_kredit/list/blocks/index.ts
Normal file
6
src/pages/pengajuan_kredit/list/blocks/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './ListToolBar';
|
||||
export * from './DateRangePicker';
|
||||
export * from './DatePicker';
|
||||
export * from './ChatMessageIn';
|
||||
export * from './ChatMessageOut';
|
||||
export * from './Notes'
|
||||
Reference in New Issue
Block a user