Client/User dapat melihat List Claim dan Total Claim
This commit is contained in:
ivan-sim
2023-10-10 10:14:12 +07:00
parent a9e6a750ad
commit 49dfb0d02b
9 changed files with 301 additions and 117 deletions

View File

@@ -25,6 +25,7 @@ class ClaimReportController extends Controller
$corporateEmployee->where('corporate_id', $corporateId); $corporateEmployee->where('corporate_id', $corporateId);
}); });
}) })
->whereHas('claim', fn ($query) => $query->where('status', 'approved'))
->where('status', 'approved') ->where('status', 'approved')
->get(); ->get();
@@ -38,10 +39,21 @@ class ClaimReportController extends Controller
->where('status', 'approved') ->where('status', 'approved')
->get(); ->get();
$disbrusments = ClaimRequest::query()
->whereHas('member', function ($query) use ($corporateId) {
$query->whereHas('employeds', function ($corporateEmployee) use ($corporateId) {
$corporateEmployee->where('corporate_id', $corporateId);
});
})
->whereHas('claim', fn ($query) => $query->where('status', 'disbrusmented'))
->where('status', 'approved')
->get();
return Helper::responseJson([ return Helper::responseJson([
'requesteds' => count($requesteds), 'requesteds' => count($requesteds),
'approveds' => count($approveds), 'approveds' => count($approveds),
'rejecteds' => count($rejecteds) 'rejecteds' => count($rejecteds),
'disbrusments' => count($disbrusments)
]); ]);
} }

View File

@@ -12,6 +12,7 @@ use Modules\Client\Transformers\ClaimReport\MemberResources as ClaimReportMember
use Modules\Client\Transformers\Dashboard\MemberResources as ClaimSubmitMemberResources; use Modules\Client\Transformers\Dashboard\MemberResources as ClaimSubmitMemberResources;
use Modules\Client\Transformers\Dashboard\MemberResources as DashboardMemberResources; use Modules\Client\Transformers\Dashboard\MemberResources as DashboardMemberResources;
use Modules\Client\Transformers\Dashboard\MemberAlarmCenterResources as DashboardMemberAlarmResources; use Modules\Client\Transformers\Dashboard\MemberAlarmCenterResources as DashboardMemberAlarmResources;
use Modules\Client\Transformers\Dashboard\MemberEmployeeDataResources as DashboardMemberEmployeeDataResources;
use Modules\Client\Transformers\DataMemberResource; use Modules\Client\Transformers\DataMemberResource;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -29,8 +30,8 @@ class CorporateMemberController extends Controller
{ {
switch ($request->input('type')) { switch ($request->input('type')) {
case 'employee-data': case 'employee-data':
$members = $this->corporateMemberService->getAllMemberAlarmCenter($corporate_id, $request); $members = $this->corporateMemberService->getAllMemberEmployeeData($corporate_id, $request);
return response()->json(Helper::paginateResources(DashboardMemberAlarmResources::collection($members))); return response()->json(Helper::paginateResources(DashboardMemberEmployeeDataResources::collection($members)));
case 'claim-report': case 'claim-report':
$members = $this->corporateMemberService->getAllMemberClaimReports($corporate_id, $request); $members = $this->corporateMemberService->getAllMemberClaimReports($corporate_id, $request);
return response()->json(Helper::paginateResources(ClaimReportMemberResources::collection($members))); return response()->json(Helper::paginateResources(ClaimReportMemberResources::collection($members)));

View File

@@ -16,13 +16,13 @@ class MemberResources extends JsonResource
{ {
return [ return [
'id' => $this->id, 'id' => $this->id,
'codeRequest' => $this->code, 'code' => $this->code,
'memberId' => $this->member_id, 'member_id' => $this->member_id,
'fullName' => $this->full_name, 'full_name' => $this->full_name,
'division' => $this->division_name ?? '', 'division_name' => $this->division_name ?? '',
'status' => $this->status, 'status' => $this->status,
'claimRequestId' => $this->claim_request_id, 'claimRequestId' => $this->claim_request_id,
'submissionDate' => $this->submission_date, 'submission_date' => $this->submission_date,
]; ];
} }
} }

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Client\Transformers\Dashboard;
use Illuminate\Http\Resources\Json\JsonResource;
class MemberEmployeeDataResources extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'personId' => $this->person_id,
'memberId' => $this->member_id,
'fullName' => $this->full_name,
'service' => $this->service_code,
'start_date' => $this->start_date,
'end_date' => $this->end_date,
'status' => $this->active,
];
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\Member;
use App\Models\Encounter; use App\Models\Encounter;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class CorporateMemberService class CorporateMemberService
{ {
@@ -47,42 +48,43 @@ class CorporateMemberService
{ {
$limit = $request->has('perPage') ? $request->input('perPage') : 10; $limit = $request->has('perPage') ? $request->input('perPage') : 10;
return Member::query() $results = DB::table('claim_requests')
->joinClaimRequests('right') ->leftJoin('claims', 'claim_requests.id', '=', 'claims.claim_request_id')
->joinCorporateEmployees('left') ->leftJoin('members', 'claim_requests.member_id', '=', 'members.id')
->joinCorporateDivisions('left') ->leftJoin('corporate_employees', 'members.id', '=', 'corporate_employees.member_id')
->with('person:id,name_prefix,name_suffix,gender,name,birth_date') ->leftJoin('corporate_divisions', 'corporate_employees.division_id', '=', 'corporate_divisions.id')
->withSum('claims', 'total_claim') ->where('corporate_employees.corporate_id', '=', $corporateId)
->whereHas('employeds', function (Builder $corporateEmployee) use ($corporateId) { ->when($request->input('search'), function ($query, $search) {
$corporateEmployee->where('corporate_id', $corporateId); $query->where(function ($query) use ($search) {
}) $query->orWhere('claim_requests.code', 'like', "%" . $search . "%")
->when($request->input('search'), function (Builder $query, $search) { ->orWhere('members.member_id', 'like', "%" . $search . "%")
$query->where(function (Builder $query) use ($search) { ->orWhere('members.name', 'like', "%" . $search . "%")
$query->orWhere('members.member_id', 'like', "%" . $search . "%") ->orWhere('corporate_divisions.name', 'like', "%" . $search . "%")
->orWhere('members.name', 'like', "%" . $search . "%"); ->orWhere('claim_requests.status', 'like', "%" . $search . "%")
}); ->orWhere('claim_requests.submission_date', 'like', "%" . $search . "%");
}) });
->when($request->input('division'), function (Builder $division, $value) { })
$division->whereHas('division', function (Builder $corporateEmployee) use ($value) { ->when($request->has('orderBy'), function ($query) use ($request) {
$corporateEmployee->where('division_id', $value); $orderBy = $request->orderBy;
}); $direction = $request->order ?? 'asc';
})
->when($request->has('orderBy'), function (Builder $query) use ($request) {
$orderBy = match ($request->orderBy) {
'memberId' => 'member_id',
'fullName' => 'name',
'codeRequest' => 'code',
default => ''
};
if (in_array($orderBy, ['member_id', 'name', 'active', 'code'])) { $query->orderBy($orderBy, $direction);
$query->getQuery()->orderBy($orderBy, $request->order); })
} elseif ($request->orderBy === 'division') { ->select('members.id', 'claim_requests.code','members.member_id', 'members.name as full_name', 'corporate_divisions.name AS division_name',
$query->getQuery()->orderBy('corporate_divisions.name', $request->order); DB::raw('
} CASE
}) WHEN claim_requests.status = "requested" THEN "requested"
->select(['members.id', 'members.person_id', 'members.member_id', 'members.name', 'corporate_divisions.name AS division_name', 'claim_requests.status', 'claim_requests.code', 'claim_requests.id AS claim_request_id','claim_requests.submission_date']) WHEN claim_requests.status = "approved" AND claims.status = "approved" THEN "approved"
->paginate($limit); WHEN claim_requests.status = "approved" AND claims.status = "declined" THEN "declined"
WHEN claim_requests.status = "approved" AND claims.status = "disbrusmented" THEN "disbrusmented"
WHEN claim_requests.status = "approved" AND claims.status = "received" THEN "pending"
WHEN claim_requests.status = "approved" AND claims.status = "received" THEN "review"
ELSE ""
END AS status
'),
'claim_requests.id AS claim_request_id', 'claim_requests.submission_date')
->paginate($limit);
return $results;
} }
public function getAllMemberClaimSubmits(int $corporateId, Request $request) public function getAllMemberClaimSubmits(int $corporateId, Request $request)
@@ -165,6 +167,44 @@ class CorporateMemberService
->paginate($limit); ->paginate($limit);
} }
public function getAllMemberEmployeeData(int $corporateId, Request $request)
{
$limit = $request->has('perPage') ? $request->input('perPage') : 10;
return Member::query()
->joinCorporateEmployees('left')
->joinMemberPlans('left')
->joinPlans('left')
->with(['currentPlan', 'person'])
->where('corporate_employees.corporate_id', $corporateId)
->when($request->input('search'), function (Builder $query, $search) {
$query->where(function (Builder $query) use ($search) {
$query->orWhere('members.member_id', 'like', "%" . $search . "%")
->orWhere('members.name', 'like', "%" . $search . "%");
});
})
->when($request->input('division'), function (Builder $query, $value) {
$query->where('corporate_employees.division_id', $value);
})
->when($request->has('orderBy'), function (Builder $query) use ($request) {
$orderBy = match ($request->input('orderBy')) {
'memberId' => 'member_id',
'fullName' => 'name',
'status' => 'active',
'start_date' => 'member_plans.start',
'end_date' => 'member_plans.end',
'service' => 'plans.service_code',
default => ''
};
$query->getQuery()->orderBy($orderBy, $request->order);
})
->select(['members.id', 'members.person_id', 'members.member_id', 'members.name', 'member_plans.start AS start_date', 'member_plans.end AS end_date', 'plans.active', 'plans.service_code'])
->selectRaw("(select sum(`claims`.`total_claim`) from `claims` where `members`.`id` = `claims`.`member_id` AND `claims`.`deleted_at` IS NULL) AS `claims_sum_total_claim`")
->paginate($limit);
}
public function getAllEncounter(int $corporateId) public function getAllEncounter(int $corporateId)
{ {
return Encounter::query()->select(['id'])->paginate(10); return Encounter::query()->select(['id'])->paginate(10);

View File

@@ -15,6 +15,7 @@ import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate
import List from './List'; import List from './List';
// theme // theme
import palette from '../../theme/palette'; import palette from '../../theme/palette';
import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs';
interface ClaimStatusType { interface ClaimStatusType {
name: string; name: string;
@@ -35,17 +36,22 @@ export default function Drugs() {
{ {
name: 'Requested', name: 'Requested',
value: claimStatus.data.data.requesteds, value: claimStatus.data.data.requesteds,
color: palette.dark.primary.dark, color: '#159C9C',
}, },
{ {
name: 'Approval', name: 'Approval',
value: claimStatus.data.data.approveds, value: claimStatus.data.data.approveds,
color: palette.dark.warning.dark, color: '#229A16',
}, },
{ {
name: 'Rejected', name: 'Disbrusment',
value: claimStatus.data.data.disbrusments,
color: '#BF6919',
},
{
name: 'Decline',
value: claimStatus.data.data.rejecteds, value: claimStatus.data.data.rejecteds,
color: palette.dark.error.dark, color: '#B72136',
}, },
]); ]);
})(); })();
@@ -54,6 +60,13 @@ export default function Drugs() {
return ( return (
<Page title="Claim Reports"> <Page title="Claim Reports">
<Container maxWidth={themeStretch ? false : 'xl'}> <Container maxWidth={themeStretch ? false : 'xl'}>
<HeaderBreadcrumbs
heading={'Claim Report'}
links={[
{ name: 'Case Management', href: '/claim-report' },
{ name: 'Claim Report', href: '/claim-report'}
]}
/>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} lg={12} md={12}> <Grid item xs={12} lg={12} md={12}>
<CardClaimStatus data={listClaimStatusItems} /> <CardClaimStatus data={listClaimStatusItems} />

View File

@@ -1,5 +1,5 @@
/* ---------------------------------- @mui ---------------------------------- */ /* ---------------------------------- @mui ---------------------------------- */
import { Stack, Button } from '@mui/material'; import { Stack, Button, MenuItem } from '@mui/material';
/* ---------------------------------- axios --------------------------------- */ /* ---------------------------------- axios --------------------------------- */
// import axios from 'axios'; // import axios from 'axios';
import axios from '../../utils/axios'; import axios from '../../utils/axios';
@@ -15,6 +15,12 @@ import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate
import { HeadCell, Order, PaginationTableProps } from '../../@types/table'; import { HeadCell, Order, PaginationTableProps } from '../../@types/table';
import { useSearchParams, useNavigate } from 'react-router-dom'; import { useSearchParams, useNavigate } from 'react-router-dom';
import { fDate } from '../../utils/formatTime'; import { fDate } from '../../utils/formatTime';
import Typography from '@mui/material/Typography';
import { format } from 'date-fns';
import TableMoreMenu from '../../components/table/TableMoreMenu';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import HistoryIcon from '@mui/icons-material/History';
import SearchIcon from '@mui/icons-material/Search';
export default function List() { export default function List() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -47,7 +53,7 @@ export default function List() {
/* ------------------------------ handle order ------------------------------ */ /* ------------------------------ handle order ------------------------------ */
const [order, setOrder] = useState<Order>('desc'); const [order, setOrder] = useState<Order>('desc');
const [orderBy, setOrderBy] = useState('codeRequest'); const [orderBy, setOrderBy] = useState('code');
const orders = { const orders = {
order: order, order: order,
@@ -109,41 +115,41 @@ export default function List() {
/* -------------------------------- headCell -------------------------------- */ /* -------------------------------- headCell -------------------------------- */
const headCells: HeadCell<never>[] = [ const headCells: HeadCell<never>[] = [
{ {
id: 'memberId', id: 'submission_date',
align: 'center',
label: 'Request Date',
isSort: true,
},
{
id: 'member_id',
align: 'left', align: 'left',
label: 'Member ID', label: 'Member ID',
isSort: true, isSort: true,
}, },
{ {
id: 'codeRequest', id: 'code',
align: 'left', align: 'left',
label: 'Code Request', label: 'Claim Code',
isSort: true, isSort: true,
}, },
{ {
id: 'submissionDate', id: 'full_name',
align: 'left',
label: 'Request Date',
isSort: true,
},
{
id: 'fullName',
align: 'left', align: 'left',
label: 'Name', label: 'Name',
isSort: true, isSort: true,
}, },
{ {
id: 'division', id: 'division_name',
align: 'left', align: 'left',
label: 'Divisi', label: 'Division',
isSort: false, isSort: true,
}, },
{ {
id: 'status', id: 'status',
align: 'center', align: 'center',
label: 'Status', label: 'Status',
isSort: false, isSort: true,
}, },
{ {
id: 'action', id: 'action',
@@ -168,49 +174,99 @@ export default function List() {
params: { ...parameters, type: 'claim-report' }, params: { ...parameters, type: 'claim-report' },
}); });
console.log(response.data.data);
setData( setData(
response.data.data.map((obj: any) => ({ response.data.data.map((obj: any) => ({
...obj, ...obj,
status: status:
obj.status === 'requested' ? ( obj.status === 'requested' ? (
<Button <Typography
onClick={() => navigate(`dialog-detail/${obj.claimRequestId}`)} variant="body2"
sx={{ sx={{
backgroundColor: 'rgba(84, 214, 44, 0.16)', width: 'Hug (6px)',
color: palette.dark.success.dark, height: 'Hug (22px)',
paddingX: 1.5, left: '862px',
paddingY: 1, top: '17px',
'&:hover': { color: '#159C9C',
backgroundColor: 'rgba(84, 214, 44, 0.16)', backgroundColor: '#00AB5529',
color: palette.dark.success.dark, padding: '1px, 8px',
}, borderRadius: '6px',
}} }}
> >
Request Request
</Button> </Typography>
) : obj.status === 'approved' ? ( ) : obj.status === 'approved' ? (
<Button <Typography
onClick={() => navigate(`dialog-detail/${obj.claimRequestId}`)} variant="body2"
sx={{ sx={{
backgroundColor: (theme) => theme.palette.secondary.main, width: 'Hug (6px)',
color: '#FFFF', height: 'Hug (22px)',
paddingX: 1.5, left: '862px',
paddingY: 1, top: '17px',
'&:hover': { color: '#229A16',
backgroundColor: (theme) => theme.palette.secondary.dark, backgroundColor: '#54D62C29',
color: '#FFFF', padding: '1px, 8px',
}, borderRadius: '6px',
}} }}
> >
Approved Approval
</Button> </Typography>
) : obj.status === 'declined' ? (
<Typography
variant="body2"
sx={{
width: 'Hug (6px)',
height: 'Hug (22px)',
left: '862px',
top: '17px',
color: '#B72136',
backgroundColor: '#FF484229',
padding: '1px, 8px',
borderRadius: '6px',
}}
>
Decline
</Typography>
) : obj.status === 'pending' ? (
<Typography
variant="body2"
sx={{
width: 'Hug (6px)',
height: 'Hug (22px)',
left: '862px',
top: '17px',
color: '#BF6919',
backgroundColor: '#FFC10729',
padding: '1px, 8px',
borderRadius: '6px',
}}
>
Pending
</Typography>
) : obj.status === 'review' ? (
<Typography
variant="body2"
sx={{
width: 'Hug (6px)',
height: 'Hug (22px)',
left: '862px',
top: '17px',
color: '#0C53B7',
backgroundColor: '#1890FF29',
padding: '1px, 8px',
borderRadius: '6px',
}}
>
Review
</Typography>
) : ( ) : (
<Button <Button
startIcon={<Iconify icon="fa6-solid:clock" />} startIcon={<Iconify icon="fa6-solid:clock" />}
sx={{ sx={{
backgroundColor: '#CD7B2E', backgroundColor: '#CD7B2E',
color: '#FFFF', color: '#FFFF',
paddingX: 1.5, padding: '1px, 8px',
paddingY: 1, paddingY: 1,
'&:hover': { '&:hover': {
backgroundColor: '#BF6919', backgroundColor: '#BF6919',
@@ -221,8 +277,31 @@ export default function List() {
Ongoing Ongoing
</Button> </Button>
), ),
submissionDate: submission_date:
obj.submissionDate ? fDate(obj.submissionDate) : '' <Typography
sx={{
backgroundColor: (theme) => theme.palette.grey[300],
borderRadius: '4px',
width: '70%',
}}
variant="body2"
>
{obj.submission_date ? format(new Date(obj.submission_date), "d MMM yyyy") : ''}
</Typography>
,
action:
<TableMoreMenu actions={
<>
<MenuItem onClick={() => navigate ('/employee-data/user-profile/'+obj.personId)}>
<SearchIcon />
Detail
</MenuItem>
<MenuItem onClick={() => navigate ('/employee-data/user-profile/'+obj.personId)}>
<HistoryIcon />
History
</MenuItem>
</>
} />
})) }))
); );

View File

@@ -250,24 +250,24 @@ export default function List() {
{ {
id: 'start_date', id: 'start_date',
align: 'left', align: 'center',
label: 'Start Date', label: 'Start Date',
isSort: true, isSort: true,
}, },
{ {
id: 'end_date', id: 'end_date',
align: 'left', align: 'center',
label: 'End Date', label: 'End Date',
isSort: true, isSort: true,
}, },
{ {
id: 'status', id: 'status',
align: 'left', align: 'center',
label: 'Status', label: 'Status',
isSort: true, isSort: true,
}, },
{ {
id: 'view', id: 'action',
align: 'center', align: 'center',
label: '', label: '',
isSort: true, isSort: true,
@@ -289,9 +289,7 @@ export default function List() {
const response = await axios.get(`${corporateValue}/members?type=employee-data`, { const response = await axios.get(`${corporateValue}/members?type=employee-data`, {
params: { ...parameters }, params: { ...parameters },
}); });
console.log(response.data.data);
setData( setData(
response.data.data.map((obj: any) => { response.data.data.map((obj: any) => {
return { return {
@@ -303,34 +301,48 @@ export default function List() {
// , // ,
status: status:
obj.status === 1 ? ( obj.status === 1 ? (
<Button <Typography
variant="outlined" variant="body2"
color="success" sx={{
size="small" width: 'Hug (6px)',
sx={{cursor:'default'}} height: 'Hug (22px)',
left: '862px',
top: '17px',
color: '#229A16',
backgroundColor: '#54D62C29',
padding: '1px, 8px',
borderRadius: '6px',
}}
> >
Active Active
</Button> </Typography>
) : ( ) : (
<Button <Typography
variant="outlined" variant="body2"
color="error" sx={{
size="small" width: 'Hug (6px)',
sx={{cursor:'default'}} height: 'Hug (22px)',
left: '862px',
top: '17px',
color: '#B72136',
backgroundColor: '#FF484229',
padding: '1px, 8px',
borderRadius: '6px',
}}
> >
Inactive Inactive
</Button> </Typography>
), ),
start_date: start_date:
<Typography <Typography
sx={{ sx={{
backgroundColor: (theme) => theme.palette.grey[300], backgroundColor: (theme) => theme.palette.grey[300],
borderRadius: '4px', borderRadius: '4px',
width: '95%', width: '70%',
}} }}
variant="body2" variant="body2"
> >
{obj.start_date ? format(new Date(obj.start_date), "dd MMMM yyyy HH:mm:ss") : ''} {obj.start_date ? format(new Date(obj.start_date), "dd MMM yyyy") : ''}
</Typography> </Typography>
, ,
end_date: end_date:
@@ -338,11 +350,11 @@ export default function List() {
sx={{ sx={{
backgroundColor: (theme) => theme.palette.grey[300], backgroundColor: (theme) => theme.palette.grey[300],
borderRadius: '4px', borderRadius: '4px',
width: '95%', width: '70%',
}} }}
variant="body2" variant="body2"
> >
{obj.end_date ? format(new Date(obj.end_date), "d MMMM yyyy HH:mm:ss") : ''} {obj.end_date ? format(new Date(obj.end_date), "d MMM yyyy") : ''}
</Typography> </Typography>
, ,
fullName: fullName:
@@ -359,7 +371,7 @@ export default function List() {
{obj.memberId} {obj.memberId}
</Typography> </Typography>
, ,
view: action:
<TableMoreMenu actions={ <TableMoreMenu actions={
<> <>
<MenuItem onClick={() => navigate ('/employee-data/user-profile/'+obj.personId)}> <MenuItem onClick={() => navigate ('/employee-data/user-profile/'+obj.personId)}>

View File

@@ -39,13 +39,13 @@ const defaultData = [
export default function CardClaimStatus({ data }: PropsCardClaimStatus) { export default function CardClaimStatus({ data }: PropsCardClaimStatus) {
return ( return (
<RootStyle> <RootStyle>
<Stack sx={{ mb: 1 }}> {/*<Stack sx={{ mb: 1 }}>
<Typography variant="body2">Claim Status</Typography> <Typography variant="body2">Claim Status</Typography>
</Stack> </Stack>*/}
<Grid container spacing={2}> <Grid container spacing={2}>
{data {data
? data.map(({ name, value, color }: ClaimStatusType, key) => ( ? data.map(({ name, value, color }: ClaimStatusType, key) => (
<Grid item key={key} xs={6} sm={4}> <Grid item key={key} xs={12} sm={3}>
<Card <Card
sx={{ sx={{
paddingX: 1, paddingX: 1,
@@ -71,7 +71,7 @@ export default function CardClaimStatus({ data }: PropsCardClaimStatus) {
</Grid> </Grid>
)) ))
: defaultData.map(({ name, value, color }: ClaimStatusType, key) => ( : defaultData.map(({ name, value, color }: ClaimStatusType, key) => (
<Grid item key={key} xs={6} sm={4}> <Grid item key={key} xs={12} sm={3}>
<Card <Card
sx={{ sx={{
paddingX: 1, paddingX: 1,