feat: tambah fitur lampiran file pada tugas kegiatan dan tugas divisi
This commit is contained in:
@@ -1,48 +1,123 @@
|
||||
import Styles from "@/constants/Styles";
|
||||
import { useTheme } from "@/providers/ThemeProvider";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { Pressable, View } from "react-native";
|
||||
import { useState } from "react";
|
||||
import { LayoutChangeEvent, Pressable, View } from "react-native";
|
||||
import Text from "./Text";
|
||||
|
||||
type FileItem = {
|
||||
name: string
|
||||
extension: string
|
||||
}
|
||||
|
||||
type Props = {
|
||||
done?: boolean
|
||||
title: string
|
||||
dateStart: string
|
||||
dateEnd: string
|
||||
files?: FileItem[]
|
||||
onPress?: () => void
|
||||
}
|
||||
|
||||
export default function ItemSectionTanggalTugas({ done, title, dateStart, dateEnd, onPress }: Props) {
|
||||
const { colors } = useTheme();
|
||||
// estimasi lebar chip berdasarkan panjang teks
|
||||
const CHAR_W = 6.5 // lebar rata-rata per karakter (font size 10)
|
||||
const ICON_W = 17 // icon 13px + margin 4px
|
||||
const PAD_H = 16 // paddingHorizontal 8 * 2
|
||||
const GAP = 6
|
||||
const PLUS_W = 72 // lebar chip "+X lainnya"
|
||||
|
||||
function estimateChipWidth(label: string) {
|
||||
return PAD_H + ICON_W + label.length * CHAR_W
|
||||
}
|
||||
|
||||
function getVisibleChips(files: FileItem[], containerWidth: number) {
|
||||
if (containerWidth === 0) return { visible: [], extra: files.length }
|
||||
|
||||
let used = 0
|
||||
const visible: FileItem[] = []
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const label = `${files[i].name}.${files[i].extension}`
|
||||
const chipW = estimateChipWidth(label)
|
||||
const isLast = i === files.length - 1
|
||||
const plusChipW = isLast ? 0 : PLUS_W + GAP
|
||||
const gapW = visible.length > 0 ? GAP : 0
|
||||
|
||||
if (used + gapW + chipW + plusChipW <= containerWidth) {
|
||||
visible.push(files[i])
|
||||
used += gapW + chipW
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return { visible, extra: files.length - visible.length }
|
||||
}
|
||||
|
||||
function getFileIcon(extension: string): keyof typeof MaterialCommunityIcons.glyphMap {
|
||||
const ext = extension.toLowerCase()
|
||||
if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif'].includes(ext)) return 'image-outline'
|
||||
if (ext === 'pdf') return 'file-pdf-box'
|
||||
if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return 'video-outline'
|
||||
if (['doc', 'docx'].includes(ext)) return 'file-word-outline'
|
||||
if (['xls', 'xlsx'].includes(ext)) return 'file-excel-outline'
|
||||
if (['zip', 'rar', '7z'].includes(ext)) return 'zip-box-outline'
|
||||
return 'file-outline'
|
||||
}
|
||||
|
||||
const chipStyle = (colors: any) => ({
|
||||
flexDirection: 'row' as const,
|
||||
alignItems: 'center' as const,
|
||||
backgroundColor: colors.dimmed + '5',
|
||||
borderRadius: 6,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.icon + '20',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
})
|
||||
|
||||
export default function ItemSectionTanggalTugas({ done, title, dateStart, dateEnd, files = [], onPress }: Props) {
|
||||
const { colors } = useTheme()
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
|
||||
const { visible, extra } = getVisibleChips(files, containerWidth)
|
||||
|
||||
function onRowLayout(e: LayoutChangeEvent) {
|
||||
const w = e.nativeEvent.layout.width
|
||||
if (w !== containerWidth) setContainerWidth(w)
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable style={[Styles.mb15, { borderBottomColor: colors.icon + '20', borderBottomWidth: 1 }]} onPress={onPress}>
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
{
|
||||
done != undefined ?
|
||||
done ?
|
||||
<>
|
||||
<MaterialCommunityIcons name="checkbox-marked-circle-outline" size={22} color={colors.text} style={[Styles.mr10]} />
|
||||
<Text>Selesai</Text>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<MaterialCommunityIcons name="checkbox-blank-circle-outline" size={22} color={colors.text} style={[Styles.mr10]} />
|
||||
<Text>Belum Selesai</Text>
|
||||
</>
|
||||
:
|
||||
<></>
|
||||
}
|
||||
|
||||
{/* Status */}
|
||||
<View style={[Styles.rowItemsCenter]}>
|
||||
{done != undefined && (
|
||||
done ? (
|
||||
<>
|
||||
<MaterialCommunityIcons name="checkbox-marked-circle-outline" size={22} color={colors.text} style={[Styles.mr10]} />
|
||||
<Text>Selesai</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MaterialCommunityIcons name="checkbox-blank-circle-outline" size={22} color={colors.text} style={[Styles.mr10]} />
|
||||
<Text>Belum Selesai</Text>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Judul tugas */}
|
||||
<View style={[Styles.wrapPaper, Styles.noShadow, Styles.borderAll, Styles.mv10, Styles.p10, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
|
||||
<View style={[Styles.rowItemsCenter, { alignItems: 'flex-start' }]}>
|
||||
<MaterialCommunityIcons name="file-table-outline" size={25} color={colors.text} style={[Styles.mr10]} />
|
||||
<MaterialCommunityIcons name="file-table-outline" size={22} color={colors.text} style={[Styles.mr10]} />
|
||||
<View style={[Styles.w90]}>
|
||||
<Text style={[Styles.textDefault]}>{title}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Tanggal */}
|
||||
<View style={[Styles.rowSpaceBetween, Styles.mb15]}>
|
||||
<View style={[{ width: '48%' }]}>
|
||||
<Text style={[Styles.mb05]}>Tanggal Mulai</Text>
|
||||
@@ -57,6 +132,52 @@ export default function ItemSectionTanggalTugas({ done, title, dateStart, dateEn
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Lampiran file */}
|
||||
{files.length > 0 && (
|
||||
<View style={[Styles.mb15]}>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mb05]}>
|
||||
<MaterialCommunityIcons name="paperclip" size={13} color={colors.dimmed} style={[Styles.mr10]} />
|
||||
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed }]}>
|
||||
{files.length} Lampiran
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{ flexDirection: 'row', gap: GAP, overflow: 'hidden' }}
|
||||
onLayout={onRowLayout}
|
||||
>
|
||||
{visible.map((file, index) => {
|
||||
const label = `${file.name}.${file.extension}`
|
||||
const chipW = Math.min(estimateChipWidth(label), containerWidth * 0.55)
|
||||
return (
|
||||
<View key={index} style={[chipStyle(colors), { width: chipW }]}>
|
||||
<MaterialCommunityIcons
|
||||
name={getFileIcon(file.extension)}
|
||||
size={13}
|
||||
color={colors.dimmed}
|
||||
style={{ marginRight: 4 }}
|
||||
/>
|
||||
<Text
|
||||
style={[Styles.textSmallSemiBold, { color: colors.dimmed, flex: 1 }]}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
{extra > 0 && (
|
||||
<View style={[chipStyle(colors)]}>
|
||||
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed }]}>
|
||||
+{extra} lainnya
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type Props = {
|
||||
dateStart: string;
|
||||
dateEnd: string;
|
||||
createdAt: string;
|
||||
files?: { name: string; extension: string }[];
|
||||
};
|
||||
|
||||
export default function SectionTanggalTugasProject({ status, member, refreshing }: { status: number | undefined, member: boolean, refreshing?: boolean }) {
|
||||
@@ -144,12 +145,9 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
|
||||
title={item.title}
|
||||
dateStart={item.dateStart}
|
||||
dateEnd={item.dateEnd}
|
||||
files={item.files ?? []}
|
||||
onPress={() => {
|
||||
if (status == 3 || (!member && (entityUser.role == "user" || entityUser.role == "coadmin"))) return
|
||||
setTugas({
|
||||
id: item.id,
|
||||
status: item.status
|
||||
})
|
||||
setTugas({ id: item.id, status: item.status })
|
||||
setModal(true)
|
||||
}}
|
||||
/>
|
||||
@@ -169,65 +167,54 @@ export default function SectionTanggalTugasProject({ status, member, refreshing
|
||||
>
|
||||
<View style={Styles.rowItemsCenter}>
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="list-status"
|
||||
color={colors.text}
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Update Status"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
setTimeout(() => {
|
||||
setSelect(true);
|
||||
}, 600)
|
||||
}}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="pencil-outline"
|
||||
color={colors.text}
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
title="Edit Tugas"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
router.push(`/project/update/${tugas.id}`);
|
||||
}}
|
||||
/>
|
||||
|
||||
<MenuItemRow
|
||||
icon={
|
||||
<MaterialCommunityIcons
|
||||
name="clock-time-three-outline"
|
||||
color={colors.text}
|
||||
size={25}
|
||||
/>
|
||||
}
|
||||
icon={<MaterialCommunityIcons name="clock-time-three-outline" color={colors.text} size={25} />}
|
||||
title="Detail Waktu"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
setTimeout(() => {
|
||||
setModalDetail(true)
|
||||
}, 600)
|
||||
setTimeout(() => setModalDetail(true), 600)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash-outline" color={colors.text} size={25} />}
|
||||
title="Hapus Tugas"
|
||||
icon={<MaterialCommunityIcons name="file-multiple-outline" color={colors.text} size={25} />}
|
||||
title="File Tugas"
|
||||
onPress={() => {
|
||||
setModal(false)
|
||||
setTimeout(() => {
|
||||
setShowDeleteModal(true)
|
||||
}, 600)
|
||||
setModal(false);
|
||||
router.push(`/project/${id}/tugas-file/${tugas.id}?member=${member}`);
|
||||
}}
|
||||
/>
|
||||
{(member || (entityUser.role != "user" && entityUser.role != "coadmin")) && status != 3 && (
|
||||
<>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="list-status" color={colors.text} size={25} />}
|
||||
title="Update Status"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
setTimeout(() => setSelect(true), 600)
|
||||
}}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="pencil-outline" color={colors.text} size={25} />}
|
||||
title="Edit Tugas"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
router.push(`/project/update/${tugas.id}`);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
{(member || (entityUser.role != "user" && entityUser.role != "coadmin")) && status != 3 && (
|
||||
<View style={[Styles.rowItemsCenter, Styles.mt15]}>
|
||||
<MenuItemRow
|
||||
icon={<Ionicons name="trash-outline" color={colors.text} size={25} />}
|
||||
title="Hapus Tugas"
|
||||
onPress={() => {
|
||||
setModal(false)
|
||||
setTimeout(() => setShowDeleteModal(true), 600)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</DrawerBottom>
|
||||
|
||||
<ModalConfirmation
|
||||
|
||||
@@ -25,6 +25,7 @@ type Props = {
|
||||
status: number;
|
||||
dateStart: string;
|
||||
dateEnd: string;
|
||||
files?: { name: string; extension: string }[];
|
||||
}
|
||||
|
||||
export default function SectionTanggalTugasTask({ refreshing, isMemberDivision }: { refreshing: boolean, isMemberDivision: boolean }) {
|
||||
@@ -145,6 +146,7 @@ export default function SectionTanggalTugasTask({ refreshing, isMemberDivision }
|
||||
title={item.title}
|
||||
dateStart={item.dateStart}
|
||||
dateEnd={item.dateEnd}
|
||||
files={item.files ?? []}
|
||||
onPress={() => {
|
||||
setTugas({
|
||||
id: item.id,
|
||||
@@ -179,6 +181,14 @@ export default function SectionTanggalTugasTask({ refreshing, isMemberDivision }
|
||||
}, 600)
|
||||
}}
|
||||
/>
|
||||
<MenuItemRow
|
||||
icon={<MaterialCommunityIcons name="file-multiple-outline" color={colors.text} size={25} />}
|
||||
title="File Tugas"
|
||||
onPress={() => {
|
||||
setModal(false);
|
||||
router.push(`/division/${id}/task/${detail}/tugas-file/${tugas.id}?member=${isMemberDivision}`)
|
||||
}}
|
||||
/>
|
||||
{
|
||||
(entityUser.role != "user" && entityUser.role != "coadmin") || isMemberDivision
|
||||
?
|
||||
|
||||
Reference in New Issue
Block a user