Compare commits

...

1 Commits

Author SHA1 Message Date
7bc546e985 Fix Responsive 2026-03-06 16:19:01 +08:00
4 changed files with 230 additions and 212 deletions

View File

@@ -92,10 +92,10 @@ const MusicPlayer = () => {
} }
return ( return (
<Box px={{ base: 'md', md: 100 }} py="xl"> <Box px={{ base: 'xs', sm: 'md', md: 100 }} py="xl">
<Paper <Paper
mx="auto" mx="auto"
p="xl" p={{ base: 'md', sm: 'xl' }}
radius="lg" radius="lg"
shadow="sm" shadow="sm"
bg="white" bg="white"
@@ -105,42 +105,52 @@ const MusicPlayer = () => {
> >
<Stack gap="md"> <Stack gap="md">
<BackButton /> <BackButton />
<Group justify="space-between" mb="xl" mt={"md"}> <Flex
justify="space-between"
align={{ base: 'flex-start', sm: 'center' }}
direction={{ base: 'column', sm: 'row' }}
gap="md"
mb="xl"
mt="md"
>
<div> <div>
<Text size="32px" fw={700} c="#0B4F78">Selamat Datang Kembali</Text> <Text fz={{ base: '24px', sm: '32px' }} fw={700} c="#0B4F78" lh={1.2}>Selamat Datang Kembali</Text>
<Text size="md" c="#5A6C7D">Temukan musik favorit Anda hari ini</Text> <Text size="sm" c="#5A6C7D">Temukan musik favorit Anda hari ini</Text>
</div> </div>
<Group gap="md"> <TextInput
<TextInput placeholder="Cari lagu..."
placeholder="Cari lagu..." leftSection={<IconSearch size={18} />}
leftSection={<IconSearch size={18} />} radius="xl"
radius="xl" w={{ base: '100%', sm: 280 }}
w={280} value={search}
value={search} onChange={(e) => setSearch(e.target.value)}
onChange={(e) => setSearch(e.target.value)} styles={{ input: { backgroundColor: '#fff' } }}
styles={{ input: { backgroundColor: '#fff' } }} />
/> </Flex>
</Group>
</Group>
<Stack gap="xl"> <Stack gap="xl">
<div> <div>
<Text size="xl" fw={700} c="#0B4F78" mb="md">Sedang Diputar</Text> <Text size="xl" fw={700} c="#0B4F78" mb="md">Sedang Diputar</Text>
{currentSong ? ( {currentSong ? (
<Card radius="md" p="xl" shadow="md"> <Card radius="md" p={{ base: 'md', sm: 'xl' }} shadow="md" withBorder>
<Group align="center" gap="xl"> <Flex
direction={{ base: 'column', sm: 'row' }}
align="center"
gap={{ base: 'md', sm: 'xl' }}
>
<Avatar <Avatar
src={currentSong.coverImage?.link || '/mp3-logo.png'} src={currentSong.coverImage?.link || '/mp3-logo.png'}
size={180} size={120}
radius="md" radius="md"
/> />
<Stack gap="md" style={{ flex: 1 }}> <Stack gap="md" style={{ flex: 1, width: '100%' }}>
<div> <Box ta={{ base: 'center', sm: 'left' }}>
<Text size="28px" fw={700} c="#0B4F78">{currentSong.judul}</Text> <Text fz={{ base: '20px', sm: '28px' }} fw={700} c="#0B4F78" lineClamp={1}>{currentSong.judul}</Text>
<Text size="lg" c="#5A6C7D">{currentSong.artis}</Text> <Text size="lg" c="#5A6C7D">{currentSong.artis}</Text>
{currentSong.genre && ( {currentSong.genre && (
<Badge mt="xs" color="#0B4F78" variant="light">{currentSong.genre}</Badge> <Badge mt="xs" color="#0B4F78" variant="light">{currentSong.genre}</Badge>
)} )}
</div> </Box>
<Group gap="xs" align="center"> <Group gap="xs" align="center">
<Text size="xs" c="#5A6C7D" w={42}>{formatTime(currentTime)}</Text> <Text size="xs" c="#5A6C7D" w={42}>{formatTime(currentTime)}</Text>
<Slider <Slider
@@ -155,7 +165,7 @@ const MusicPlayer = () => {
<Text size="xs" c="#5A6C7D" w={42}>{formatTime(duration || 0)}</Text> <Text size="xs" c="#5A6C7D" w={42}>{formatTime(duration || 0)}</Text>
</Group> </Group>
</Stack> </Stack>
</Group> </Flex>
</Card> </Card>
) : ( ) : (
<Card radius="md" p="xl" shadow="md"> <Card radius="md" p="xl" shadow="md">
@@ -175,28 +185,29 @@ const MusicPlayer = () => {
<Grid.Col span={{ base: 12, sm: 6, lg: 4 }} key={song.id}> <Grid.Col span={{ base: 12, sm: 6, lg: 4 }} key={song.id}>
<Card <Card
radius="md" radius="md"
p="md" p="sm"
shadow="sm" shadow="sm"
withBorder
style={{ style={{
cursor: 'pointer', cursor: 'pointer',
border: currentSong?.id === song.id ? '2px solid #0B4F78' : '2px solid transparent', borderColor: currentSong?.id === song.id ? '#0B4F78' : 'transparent',
backgroundColor: currentSong?.id === song.id ? '#F0F7FA' : 'white',
transition: 'all 0.2s' transition: 'all 0.2s'
}} }}
onClick={() => playSong(song)} onClick={() => playSong(song)}
> >
<Group gap="md" align="center"> <Group gap="sm" align="center" wrap="nowrap">
<Avatar <Avatar
src={song.coverImage?.link || 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=400&h=400&fit=crop'} src={song.coverImage?.link || '/mp3-logo.png'}
size={64} size={50}
radius="md" radius="md"
/> />
<Stack gap={4} style={{ flex: 1, minWidth: 0 }}> <Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
<Text size="sm" fw={600} c="#0B4F78" truncate>{song.judul}</Text> <Text size="sm" fw={600} c="#0B4F78" truncate>{song.judul}</Text>
<Text size="xs" c="#5A6C7D">{song.artis}</Text> <Text size="xs" c="#5A6C7D" truncate>{song.artis}</Text>
<Text size="xs" c="#8A9BA8">{song.durasi}</Text>
</Stack> </Stack>
{currentSong?.id === song.id && isPlaying && ( {currentSong?.id === song.id && isPlaying && (
<Badge color="#0B4F78" variant="filled">Memutar</Badge> <Badge color="#0B4F78" variant="filled" size="xs">Playing</Badge>
)} )}
</Group> </Group>
</Card> </Card>
@@ -207,34 +218,42 @@ const MusicPlayer = () => {
)} )}
</div> </div>
</Stack> </Stack>
</Stack> </Stack>
</Paper> </Paper>
{/* Control Player Section */}
<Paper <Paper
mt="xl" mt="xl"
mx="auto" mx="auto"
p="xl" p={{ base: 'md', sm: 'xl' }}
radius="lg" radius="lg"
shadow="sm" shadow="sm"
bg="white" bg="white"
style={{ style={{
border: '1px solid #eaeaea', border: '1px solid #eaeaea',
position: 'sticky',
bottom: 20,
zIndex: 10
}} }}
> >
<Flex align="center" justify="space-between" gap="xl" h="100%"> <Flex
<Group gap="md" style={{ flex: 1 }}> direction={{ base: 'column', md: 'row' }}
align="center"
justify="space-between"
gap={{ base: 'md', md: 'xl' }}
>
{/* Song Info */}
<Group gap="md" style={{ flex: 1, width: '100%' }} wrap="nowrap">
<Avatar <Avatar
src={currentSong?.coverImage?.link || 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=400&h=400&fit=crop'} src={currentSong?.coverImage?.link || '/mp3-logo.png'}
size={56} size={48}
radius="md" radius="md"
/> />
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0 }}>
{currentSong ? ( {currentSong ? (
<> <>
<Text size="sm" fw={600} c="#0B4F78" truncate>{currentSong.judul}</Text> <Text size="sm" fw={600} c="#0B4F78" truncate>{currentSong.judul}</Text>
<Text size="xs" c="#5A6C7D">{currentSong.artis}</Text> <Text size="xs" c="#5A6C7D" truncate>{currentSong.artis}</Text>
</> </>
) : ( ) : (
<Text size="sm" c="dimmed">Tidak ada lagu</Text> <Text size="sm" c="dimmed">Tidak ada lagu</Text>
@@ -242,29 +261,31 @@ const MusicPlayer = () => {
</div> </div>
</Group> </Group>
<Stack gap="xs" style={{ flex: 1 }} align="center"> {/* Controls + Progress */}
<Group gap="md"> <Stack gap="xs" style={{ flex: 2, width: '100%' }} align="center">
<Group gap="sm">
<ActionIcon <ActionIcon
variant={isShuffle ? 'filled' : 'subtle'} variant={isShuffle ? 'filled' : 'subtle'}
color="#0B4F78" color="#0B4F78"
onClick={toggleShuffleHandler} onClick={toggleShuffleHandler}
radius="xl" radius="xl"
size={48}
> >
{isShuffle ? <IconArrowsShuffle size={18} /> : <IconX size={18} />} {isShuffle ? <IconArrowsShuffle size={18} /> : <IconX size={18} />}
</ActionIcon> </ActionIcon>
<ActionIcon variant="light" color="#0B4F78" size={40} radius="xl" onClick={skipBack}> <ActionIcon variant="light" color="#0B4F78" size={48} radius="xl" onClick={skipBack}>
<IconPlayerSkipBackFilled size={20} /> <IconPlayerSkipBackFilled size={20} />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
variant="filled" variant="filled"
color="#0B4F78" color="#0B4F78"
size={56} size={48}
radius="xl" radius="xl"
onClick={togglePlayPauseHandler} onClick={togglePlayPauseHandler}
> >
{isPlaying ? <IconPlayerPauseFilled size={26} /> : <IconPlayerPlayFilled size={26} />} {isPlaying ? <IconPlayerPauseFilled size={26} /> : <IconPlayerPlayFilled size={26} />}
</ActionIcon> </ActionIcon>
<ActionIcon variant="light" color="#0B4F78" size={40} radius="xl" onClick={skipForward}> <ActionIcon variant="light" color="#0B4F78" size={48} radius="xl" onClick={skipForward}>
<IconPlayerSkipForwardFilled size={20} /> <IconPlayerSkipForwardFilled size={20} />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
@@ -272,6 +293,7 @@ const MusicPlayer = () => {
color="#0B4F78" color="#0B4F78"
onClick={toggleRepeatHandler} onClick={toggleRepeatHandler}
radius="xl" radius="xl"
size="md"
> >
{isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />} {isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />}
</ActionIcon> </ActionIcon>
@@ -290,7 +312,8 @@ const MusicPlayer = () => {
</Group> </Group>
</Stack> </Stack>
<Group gap="xs" style={{ flex: 1 }} justify="flex-end"> {/* Volume Control - Hidden on mobile, shown on md and up */}
<Group gap="xs" style={{ flex: 1 }} justify="flex-end" visibleFrom="md">
<ActionIcon variant="subtle" color="gray" onClick={toggleMuteHandler}> <ActionIcon variant="subtle" color="gray" onClick={toggleMuteHandler}>
{isMuted || volume === 0 ? <IconVolumeOff size={20} /> : <IconVolume size={20} />} {isMuted || volume === 0 ? <IconVolumeOff size={20} /> : <IconVolume size={20} />}
</ActionIcon> </ActionIcon>

View File

@@ -93,28 +93,19 @@ export default function FixedPlayerBar() {
mt="md" mt="md"
style={{ style={{
position: 'fixed', position: 'fixed',
top: '50%', // Menempatkan titik atas ikon di tengah layar top: '50%',
left: '0px', left: '0px',
transform: 'translateY(-50%)', // Menggeser ikon ke atas sebesar setengah tingginya sendiri agar benar-benar di tengah transform: 'translateY(-50%)',
borderBottomRightRadius: '20px', borderBottomRightRadius: '20px',
borderTopRightRadius: '20px', borderTopRightRadius: '20px',
cursor: 'pointer', cursor: 'pointer',
transition: 'transform 0.2s ease', transition: 'transform 0.2s ease',
zIndex: 1 zIndex: 1000 // Higher z-index
}} }}
onClick={handleRestorePlayer} onClick={handleRestorePlayer}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-50%) scale(1.1)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(-50%)';
}}
> >
<IconMusic size={28} color="white" /> <IconMusic size={24} color="white" />
</Button> </Button>
{/* Spacer to prevent content from being hidden behind player */}
<Box h={20} />
</> </>
); );
} }
@@ -131,132 +122,125 @@ export default function FixedPlayerBar() {
bottom={0} bottom={0}
left={0} left={0}
right={0} right={0}
p="sm" p={{ base: 'xs', sm: 'sm' }}
shadow="lg" shadow="xl"
style={{ style={{
zIndex: 1, zIndex: 1000,
borderTop: '1px solid rgba(0,0,0,0.1)', borderTop: '1px solid rgba(0,0,0,0.1)',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
backdropFilter: 'blur(10px)',
}} }}
> >
<Flex align="center" gap="md" justify="space-between"> <Flex align="center" gap={{ base: 'xs', sm: 'md' }} justify="space-between">
{/* Song Info - Left */} {/* Song Info - Left */}
<Group gap="sm" flex={1} style={{ minWidth: 0 }}> <Group gap="xs" flex={{ base: 2, sm: 1 }} style={{ minWidth: 0 }} wrap="nowrap">
<Avatar <Avatar
src={currentSong.coverImage?.link || ''} src={currentSong.coverImage?.link || ''}
alt={currentSong.judul} alt={currentSong.judul}
size={40} size={"36"}
radius="sm" radius="sm"
imageProps={{ loading: 'lazy' }}
/> />
<Box style={{ minWidth: 0 }}> <Box style={{ minWidth: 0, flex: 1 }}>
<Text fz="sm" fw={600} truncate> <Text fz={{ base: 'xs', sm: 'sm' }} fw={600} truncate>
{currentSong.judul} {currentSong.judul}
</Text> </Text>
<Text fz="xs" c="dimmed" truncate> <Text fz="10px" c="dimmed" truncate>
{currentSong.artis} {currentSong.artis}
</Text> </Text>
</Box> </Box>
</Group> </Group>
{/* Controls + Progress - Center */} {/* Controls - Center */}
<Group gap="xs" flex={2} justify="center"> <Group gap={"xs"} flex={{ base: 1, sm: 2 }} justify="center" wrap="nowrap">
{/* Control Buttons */} {/* Shuffle - Desktop Only */}
<Group gap="xs"> <ActionIcon
<ActionIcon variant={isShuffle ? 'filled' : 'subtle'}
variant={isShuffle ? 'filled' : 'subtle'} color={isShuffle ? '#0B4F78' : 'gray'}
color={isShuffle ? 'blue' : 'gray'} size={"md"}
size="lg" onClick={handleToggleShuffle}
onClick={handleToggleShuffle} visibleFrom="sm"
title="Shuffle" >
> <IconArrowsShuffle size={18} />
<IconArrowsShuffle size={18} /> </ActionIcon>
</ActionIcon>
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color="gray" color="gray"
size="lg" size={"md"}
onClick={playPrev} onClick={playPrev}
title="Previous" >
> <IconPlayerSkipBackFilled size={20} />
<IconPlayerSkipBackFilled size={20} /> </ActionIcon>
</ActionIcon>
<ActionIcon <ActionIcon
variant="filled" variant="filled"
color={isPlaying ? 'blue' : 'gray'} color="#0B4F78"
size="xl" size={"lg"}
radius="xl" radius="xl"
onClick={togglePlayPause} onClick={togglePlayPause}
title={isPlaying ? 'Pause' : 'Play'} >
> {isPlaying ? (
{isPlaying ? ( <IconPlayerPauseFilled size={24} />
<IconPlayerPauseFilled size={24} /> ) : (
) : ( <IconPlayerPlayFilled size={24} />
<IconPlayerPlayFilled size={24} /> )}
)} </ActionIcon>
</ActionIcon>
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color="gray" color="gray"
size="lg" size={"md"}
onClick={playNext} onClick={playNext}
title="Next" >
> <IconPlayerSkipForwardFilled size={20} />
<IconPlayerSkipForwardFilled size={20} /> </ActionIcon>
</ActionIcon>
<ActionIcon {/* Repeat - Desktop Only */}
variant="subtle" <ActionIcon
color={isRepeat ? 'blue' : 'gray'} variant={isRepeat ? 'filled' : 'subtle'}
size="lg" color={isRepeat ? '#0B4F78' : 'gray'}
onClick={toggleRepeat} size={"md"}
title={isRepeat ? 'Repeat On' : 'Repeat Off'} onClick={toggleRepeat}
> visibleFrom="sm"
{isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />} >
</ActionIcon> {isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />}
</Group> </ActionIcon>
{/* Progress Bar - Desktop */} {/* Progress Bar - Desktop Only */}
<Box w={200} display={{ base: 'none', md: 'block' }}> <Box w={150} ml="md" visibleFrom="md">
<Slider <Slider
value={currentTime} value={currentTime}
max={duration || 100} max={duration || 100}
onChange={handleSeek} onChange={handleSeek}
size="sm" size="xs"
color="blue" color="#0B4F78"
label={(value) => formatTime(value)} label={(value) => formatTime(value)}
/> />
</Box> </Box>
</Group> </Group>
{/* Right Controls - Volume + Close */} {/* Right Controls - Volume + Close */}
<Group gap="xs" flex={1} justify="flex-end"> <Group gap={4} flex={1} justify="flex-end" wrap="nowrap">
{/* Volume Control - Tablet/Desktop */}
<Box <Box
onMouseEnter={() => setShowVolume(true)} onMouseEnter={() => setShowVolume(true)}
onMouseLeave={() => setShowVolume(false)} onMouseLeave={() => setShowVolume(false)}
pos="relative" pos="relative"
visibleFrom="sm"
> >
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color={isMuted ? 'red' : 'gray'} color={isMuted ? 'red' : 'gray'}
size="lg" size="lg"
onClick={toggleMute} onClick={toggleMute}
title={isMuted ? 'Unmute' : 'Mute'}
> >
{isMuted ? ( {isMuted ? <IconVolumeOff size={18} /> : <IconVolume size={18} />}
<IconVolumeOff size={18} />
) : (
<IconVolume size={18} />
)}
</ActionIcon> </ActionIcon>
<Transition <Transition
mounted={showVolume} mounted={showVolume}
transition="scale-y" transition="scale-y"
duration={200} duration={200}
timingFunction="ease"
> >
{(style) => ( {(style) => (
<Paper <Paper
@@ -265,8 +249,8 @@ export default function FixedPlayerBar() {
position: 'absolute', position: 'absolute',
bottom: '100%', bottom: '100%',
right: 0, right: 0,
mb: 'xs', marginBottom: '10px',
p: 'sm', padding: '10px',
zIndex: 1001, zIndex: 1001,
}} }}
shadow="md" shadow="md"
@@ -276,8 +260,8 @@ export default function FixedPlayerBar() {
value={isMuted ? 0 : volume} value={isMuted ? 0 : volume}
max={100} max={100}
onChange={handleVolumeChange} onChange={handleVolumeChange}
h={100} h={80}
color="blue" color="#0B4F78"
size="sm" size="sm"
/> />
</Paper> </Paper>
@@ -288,30 +272,29 @@ export default function FixedPlayerBar() {
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color="gray" color="gray"
size="lg" size={"md"}
onClick={handleMinimizePlayer} onClick={handleMinimizePlayer}
title="Minimize player"
> >
<IconX size={18} /> <IconX size={18} />
</ActionIcon> </ActionIcon>
</Group> </Group>
</Flex> </Flex>
{/* Progress Bar - Mobile */} {/* Progress Bar - Mobile (Base) */}
<Box mt="xs" display={{ base: 'block', md: 'none' }}> <Box px="xs" mt={4} hiddenFrom="md">
<Slider <Slider
value={currentTime} value={currentTime}
max={duration || 100} max={duration || 100}
onChange={handleSeek} onChange={handleSeek}
size="sm" size="xs"
color="blue" color="#0B4F78"
label={(value) => formatTime(value)} label={(value) => formatTime(value)}
/> />
</Box> </Box>
</Paper> </Paper>
{/* Spacer to prevent content from being hidden behind player */} {/* Spacer to prevent content from being hidden behind player */}
<Box h={80} /> <Box h={{ base: 70, sm: 80 }} />
</> </>
); );
} }

View File

@@ -1,24 +1,28 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { Paper, Table, Title } from '@mantine/core'; import { Paper, Table, Title, Text } from '@mantine/core';
function Section({ title, data }: any) { function Section({ title, data }: any) {
if (!data || data.length === 0) return null; if (!data || data.length === 0) return null;
return ( return (
<> <>
<Table.Tr> <Table.Tr bg="gray.0">
<Table.Td colSpan={2}> <Table.Td colSpan={2}>
<strong>{title}</strong> <Text fw={700} fz={{ base: 'xs', sm: 'sm' }}>{title}</Text>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
{data.map((item: any) => ( {data.map((item: any) => (
<Table.Tr key={item.id}> <Table.Tr key={item.id}>
<Table.Td> <Table.Td>
{item.kode} - {item.uraian} <Text fz={{ base: 'xs', sm: 'sm' }} lineClamp={2}>
{item.kode} - {item.uraian}
</Text>
</Table.Td> </Table.Td>
<Table.Td ta="right"> <Table.Td ta="right">
Rp {item.anggaran.toLocaleString('id-ID')} <Text fz={{ base: 'xs', sm: 'sm' }} fw={500} style={{ whiteSpace: 'nowrap' }}>
Rp {item.anggaran.toLocaleString('id-ID')}
</Text>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
))} ))}
@@ -39,22 +43,24 @@ export default function PaguTable({ apbdesData }: any) {
const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan'); const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan');
return ( return (
<Paper withBorder p="md" radius="md"> <Paper withBorder p={{ base: 'sm', sm: 'md' }} radius="md">
<Title order={5} mb="md">{title}</Title> <Title order={5} mb="md" fz={{ base: 'sm', sm: 'md' }}>{title}</Title>
<Table> <Table.ScrollContainer minWidth={280}>
<Table.Thead> <Table verticalSpacing="xs">
<Table.Tr> <Table.Thead>
<Table.Th>Uraian</Table.Th> <Table.Tr>
<Table.Th ta="right">Anggaran (Rp)</Table.Th> <Table.Th fz={{ base: 'xs', sm: 'sm' }}>Uraian</Table.Th>
</Table.Tr> <Table.Th ta="right" fz={{ base: 'xs', sm: 'sm' }}>Anggaran (Rp)</Table.Th>
</Table.Thead> </Table.Tr>
<Table.Tbody> </Table.Thead>
<Section title="1) PENDAPATAN" data={pendapatan} /> <Table.Tbody>
<Section title="2) BELANJA" data={belanja} /> <Section title="1) PENDAPATAN" data={pendapatan} />
<Section title="3) PEMBIAYAAN" data={pembiayaan} /> <Section title="2) BELANJA" data={belanja} />
</Table.Tbody> <Section title="3) PEMBIAYAAN" data={pembiayaan} />
</Table> </Table.Tbody>
</Table>
</Table.ScrollContainer>
</Paper> </Paper>
); );
} }

View File

@@ -30,56 +30,62 @@ export default function RealisasiTable({ apbdesData }: any) {
}; };
return ( return (
<Paper withBorder p="md" radius="md"> <Paper withBorder p={{ base: 'sm', sm: 'md' }} radius="md">
<Title order={5} mb="md">{title}</Title> <Title order={5} mb="md" fz={{ base: 'sm', sm: 'md' }}>{title}</Title>
{allRealisasiRows.length === 0 ? ( {allRealisasiRows.length === 0 ? (
<Text fz="sm" c="dimmed" ta="center" py="md"> <Text fz="sm" c="dimmed" ta="center" py="md">
Belum ada data realisasi Belum ada data realisasi
</Text> </Text>
) : ( ) : (
<Table> <Table.ScrollContainer minWidth={300}>
<Table.Thead> <Table verticalSpacing="xs">
<Table.Tr> <Table.Thead>
<Table.Th>Uraian</Table.Th> <Table.Tr>
<Table.Th ta="right">Realisasi (Rp)</Table.Th> <Table.Th fz={{ base: 'xs', sm: 'sm' }}>Uraian</Table.Th>
<Table.Th ta="center">%</Table.Th> <Table.Th ta="right" fz={{ base: 'xs', sm: 'sm' }}>Realisasi (Rp)</Table.Th>
</Table.Tr> <Table.Th ta="center" fz={{ base: 'xs', sm: 'sm' }}>%</Table.Th>
</Table.Thead> </Table.Tr>
<Table.Tbody> </Table.Thead>
{allRealisasiRows.map(({ realisasi, parentItem }) => { <Table.Tbody>
const persentase = parentItem.anggaran > 0 {allRealisasiRows.map(({ realisasi, parentItem }) => {
? (realisasi.jumlah / parentItem.anggaran) * 100 const persentase = parentItem.anggaran > 0
: 0; ? (realisasi.jumlah / parentItem.anggaran) * 100
: 0;
return ( return (
<Table.Tr key={realisasi.id}> <Table.Tr key={realisasi.id}>
<Table.Td> <Table.Td>
<Text>{realisasi.kode || '-'} - {realisasi.keterangan || '-'}</Text> <Text fz={{ base: 'xs', sm: 'sm' }} lineClamp={2}>
</Table.Td> {realisasi.kode || '-'} - {realisasi.keterangan || '-'}
<Table.Td ta="right"> </Text>
<Text fw={600} c="blue"> </Table.Td>
{formatRupiah(realisasi.jumlah || 0)} <Table.Td ta="right">
</Text> <Text fw={600} c="blue" fz={{ base: 'xs', sm: 'sm' }} style={{ whiteSpace: 'nowrap' }}>
</Table.Td> {formatRupiah(realisasi.jumlah || 0)}
<Table.Td ta="center"> </Text>
<Badge </Table.Td>
color={ <Table.Td ta="center">
persentase >= 100 <Badge
? 'teal' size="sm"
: persentase >= 60 variant="light"
? 'yellow' color={
: 'red' persentase >= 100
} ? 'teal'
> : persentase >= 60
{persentase.toFixed(2)}% ? 'yellow'
</Badge> : 'red'
</Table.Td> }
</Table.Tr> >
); {persentase.toFixed(1)}%
})} </Badge>
</Table.Tbody> </Table.Td>
</Table> </Table.Tr>
);
})}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
)} )}
</Paper> </Paper>
); );