refactor: remove sm: gap
This commit is contained in:
parent
4aa1abdf0c
commit
ac373240e7
@ -1,8 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useForm } from '@inertiajs/vue3';
|
|
||||||
import { Camera, Loader2 } from '@lucide/vue';
|
|
||||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
|
||||||
import { toast } from 'vue-sonner';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -14,6 +10,10 @@ import {
|
|||||||
import { useGeolocation } from '@/composables/useGeolocation';
|
import { useGeolocation } from '@/composables/useGeolocation';
|
||||||
import { useWebcamCapture } from '@/composables/useWebcamCapture';
|
import { useWebcamCapture } from '@/composables/useWebcamCapture';
|
||||||
import type { AttendanceCaptureFormData } from '@/types/attendance';
|
import type { AttendanceCaptureFormData } from '@/types/attendance';
|
||||||
|
import { useForm } from '@inertiajs/vue3';
|
||||||
|
import { Camera, Loader2 } from '@lucide/vue';
|
||||||
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
const open = defineModel<boolean>('open', { default: false });
|
const open = defineModel<boolean>('open', { default: false });
|
||||||
|
|
||||||
@ -37,13 +37,17 @@ const form = useForm<AttendanceCaptureFormData>({
|
|||||||
location_tag: '',
|
location_tag: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = computed(() => (props.mode === 'check-in' ? 'Presensi Masuk' : 'Presensi Pulang'));
|
const title = computed(() =>
|
||||||
const submitLabel = computed(() => (props.mode === 'check-in' ? 'Konfirmasi Masuk' : 'Konfirmasi Pulang'));
|
props.mode === 'check-in' ? 'Presensi Masuk' : 'Presensi Pulang',
|
||||||
const submitUrl = computed(() => (
|
);
|
||||||
|
const submitLabel = computed(() =>
|
||||||
|
props.mode === 'check-in' ? 'Konfirmasi Masuk' : 'Konfirmasi Pulang',
|
||||||
|
);
|
||||||
|
const submitUrl = computed(() =>
|
||||||
props.mode === 'check-in'
|
props.mode === 'check-in'
|
||||||
? '/admin/hr/attendances/check-in'
|
? '/admin/hr/attendances/check-in'
|
||||||
: '/admin/hr/attendances/check-out'
|
: '/admin/hr/attendances/check-out',
|
||||||
));
|
);
|
||||||
|
|
||||||
async function initializeCamera() {
|
async function initializeCamera() {
|
||||||
if (!videoRef.value) {
|
if (!videoRef.value) {
|
||||||
@ -83,7 +87,10 @@ async function capturePhoto() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const position = await getCurrentPosition();
|
const position = await getCurrentPosition();
|
||||||
const locationTag = buildLocationTag(position.latitude, position.longitude);
|
const locationTag = buildLocationTag(
|
||||||
|
position.latitude,
|
||||||
|
position.longitude,
|
||||||
|
);
|
||||||
const photo = captureWithLocationTag(videoRef.value, locationTag);
|
const photo = captureWithLocationTag(videoRef.value, locationTag);
|
||||||
|
|
||||||
stopCamera();
|
stopCamera();
|
||||||
@ -95,7 +102,11 @@ async function capturePhoto() {
|
|||||||
form.longitude = position.longitude;
|
form.longitude = position.longitude;
|
||||||
form.location_tag = locationTag;
|
form.location_tag = locationTag;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : 'Gagal mengambil foto presensi.');
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Gagal mengambil foto presensi.',
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
capturing.value = false;
|
capturing.value = false;
|
||||||
}
|
}
|
||||||
@ -150,51 +161,82 @@ onBeforeUnmount(() => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<p v-if="!previewPhoto" class="text-muted-foreground text-sm">
|
<p v-if="!previewPhoto" class="text-sm text-muted-foreground">
|
||||||
Ambil foto langsung dari kamera. Upload dari galeri tidak diizinkan.
|
Ambil foto langsung dari kamera. Upload dari galeri tidak
|
||||||
|
diizinkan.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="!previewPhoto" class="space-y-2">
|
<div v-if="!previewPhoto" class="space-y-2">
|
||||||
<p class="text-sm font-medium">
|
<p class="text-sm font-medium">Kamera</p>
|
||||||
Kamera
|
<div
|
||||||
</p>
|
class="relative aspect-4/3 overflow-hidden rounded-lg border bg-muted"
|
||||||
<div class="relative aspect-4/3 overflow-hidden rounded-lg border bg-muted">
|
>
|
||||||
<video ref="videoRef" class="size-full scale-x-[-1] object-cover" autoplay muted playsinline />
|
<video
|
||||||
|
ref="videoRef"
|
||||||
|
class="size-full scale-x-[-1] object-cover"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-if="cameraLoading"
|
<div
|
||||||
class="absolute inset-0 flex items-center justify-center bg-background/80">
|
v-if="cameraLoading"
|
||||||
<Loader2 class="text-muted-foreground size-8 animate-spin" />
|
class="absolute inset-0 flex items-center justify-center bg-background/80"
|
||||||
|
>
|
||||||
|
<Loader2
|
||||||
|
class="size-8 animate-spin text-muted-foreground"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-2">
|
<div v-else class="space-y-2">
|
||||||
<p class="text-sm font-medium">
|
<p class="text-sm font-medium">Hasil Foto</p>
|
||||||
Hasil Foto
|
<div
|
||||||
</p>
|
class="relative aspect-4/3 overflow-hidden rounded-lg border bg-muted"
|
||||||
<div class="relative aspect-4/3 overflow-hidden rounded-lg border bg-muted">
|
>
|
||||||
<img :src="previewPhoto" alt="Hasil foto presensi" class="size-full object-cover" />
|
<img :src="previewPhoto" alt="Hasil foto presensi" class="size-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-muted-foreground text-xs whitespace-pre-line">
|
<p
|
||||||
|
class="text-xs whitespace-pre-line text-muted-foreground"
|
||||||
|
>
|
||||||
{{ form.location_tag }}
|
{{ form.location_tag }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter class="gap-2 sm:gap-0">
|
<DialogFooter>
|
||||||
<Button v-if="previewPhoto" type="button" variant="outline" @click="retakePhoto">
|
<Button
|
||||||
|
v-if="previewPhoto"
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
@click="retakePhoto"
|
||||||
|
>
|
||||||
Ambil Ulang
|
Ambil Ulang
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button v-if="!previewPhoto" type="button" :disabled="!cameraReady || capturing" @click="capturePhoto">
|
<Button
|
||||||
|
v-if="!previewPhoto"
|
||||||
|
type="button"
|
||||||
|
:disabled="!cameraReady || capturing"
|
||||||
|
@click="capturePhoto"
|
||||||
|
>
|
||||||
<Loader2 v-if="capturing" class="size-4 animate-spin" />
|
<Loader2 v-if="capturing" class="size-4 animate-spin" />
|
||||||
<Camera v-else class="size-4" />
|
<Camera v-else class="size-4" />
|
||||||
Ambil Foto
|
Ambil Foto
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button v-else type="button" :disabled="form.processing" @click="submitAttendance">
|
<Button
|
||||||
<Loader2 v-if="form.processing" class="size-4 animate-spin" />
|
v-else
|
||||||
|
type="button"
|
||||||
|
:disabled="form.processing"
|
||||||
|
@click="submitAttendance"
|
||||||
|
>
|
||||||
|
<Loader2
|
||||||
|
v-if="form.processing"
|
||||||
|
class="size-4 animate-spin"
|
||||||
|
/>
|
||||||
{{ submitLabel }}
|
{{ submitLabel }}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user