upload image

This commit is contained in:
='fauz 2025-10-16 18:10:46 +07:00
parent 2c94f9cf30
commit b36c54eb60
8 changed files with 123 additions and 40 deletions

8
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.1.0",
"dependencies": {
"axios": "^1.12.2",
"core-js": "^3.8.3",
"core-js": "^3.46.0",
"pinia": "^3.0.3",
"primeicons": "^7.0.0",
"vue": "^3.2.13",
@ -4973,9 +4973,9 @@
}
},
"node_modules/core-js": {
"version": "3.45.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz",
"integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==",
"version": "3.46.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz",
"integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==",
"hasInstallScript": true,
"license": "MIT",
"funding": {

View File

@ -10,7 +10,7 @@
},
"dependencies": {
"axios": "^1.12.2",
"core-js": "^3.8.3",
"core-js": "^3.46.0",
"pinia": "^3.0.3",
"primeicons": "^7.0.0",
"vue": "^3.2.13",

View File

@ -46,7 +46,7 @@
class="flex flex-col items-center"
>
<img
:src="page.image"
:src="page.media"
:alt="'Halaman ' + (index + 1)"
class="w-full rounded-xl shadow-lg border border-white/40 object-contain"
/>
@ -68,6 +68,7 @@
<script>
import router from '@/router'
import api from '@/util/api'
// import { forEach } from 'core-js/core/array';
export default {
name: "MangaPageScrollReader",
@ -112,7 +113,11 @@ export default {
if (this.pages.length > 0 && this.pages[0].chapter?.manga?.title) {
this.mangaTitle = this.pages[0].chapter.manga.title
}
console.log(this.pages)
this.pages.forEach((page) => {
console.log("Pages :" + JSON.stringify(page))
});
} catch (err) {
console.error("Gagal memuat halaman:", err)
} finally {

View File

@ -46,7 +46,7 @@
<!-- Manga Cover -->
<div class="relative" @click="toPage(chapter.id,chapter.chapter)">
<img
:src="images.find(image => image.id === chapter.id).image"
:src="chapter.manga?.featured_image"
:alt="chapter.status"
class="w-full h-48 object-cover rounded-xl group-hover:scale-105 transition-transform duration-500"
/>
@ -95,7 +95,7 @@ export default {
router.back();
},
toPage(manga_id,chapter_id) {
router.push(`/entertainment/manga/${manga_id}/chapters/${chapter_id}/`);
router.push(`/entertainment/manga/${manga_id}/chapters/${chapter_id}/pages`);
},
async loadContent() {
const res = await getChapters(this.id);

View File

@ -183,7 +183,6 @@
>
Status: {{ mission.userStatus }}
</p>
<div class="flex items-center gap-4 text-xs text-gray-700 pt-2">
<span class="flex items-center gap-1">
<svg class="w-4 h-4 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
@ -191,7 +190,7 @@
d="M10 2a8 8 0 100 16 8 8 0 000-16zM9 11V5a1 1 0 012 0v6a1 1 0 01-2 0zM10 13a1 1 0 100 2 1 1 0 000-2z"
/>
</svg>
{{ timeLeft(mission.date_valid, mission.time_to_valid) }}
{{ timeLeft(mission.date_from,mission.date_to, mission.time_from_valid, mission.time_to_valid) }}
</span>
<span class="flex items-center gap-1">
@ -205,7 +204,6 @@
</svg>
+{{ mission.coin }} koin
</span>
<span class="flex items-center gap-1">
<svg
class="w-4 h-4 text-green-600 transform rotate-180"
@ -224,6 +222,7 @@
<div class="flex items-center gap-4 text-xs text-gray-700">
<button
v-if="mission.userStatus !== 'completed'"
:disabled="!isMissionActive(mission.date_from, mission.time_from_valid)"
class="w-full mt-4 py-2 px-4 rounded-lg bg-red-600 text-white font-bold hover:bg-green-700 transition"
@click="handleMissionClick(mission)"
>
@ -330,8 +329,11 @@
</template>
<script>
// import { computed } from "vue"
import { getContent } from '@/services/content';
import { getMissions,getMissionLogs, createMissionLog } from '@/services/missions';
import { useAuthStore } from '@/stores/auth'
export default {
name: "App",
data() {
@ -363,7 +365,9 @@ export default {
now : new Date(),
timer: null,
misi:[],
logs:[]
logs:[],
authStore : useAuthStore(),
// currentUser : computed(() => this.authStore.currentUser)
};
},
methods: {
@ -379,6 +383,12 @@ export default {
this.selectedProfile = profile;
this.showModal = false;
},
isMissionActive(dateFrom, timeFrom) {
if (!dateFrom || !timeFrom) return false;
const start = new Date(`${dateFrom}T${timeFrom}`);
const now = this.now ? new Date(this.now) : new Date();
return now >= start;
},
async getMission() {
const res = await getMissions();
this.missions = res.results;
@ -404,13 +414,14 @@ export default {
return "—";
}
},
timeLeft(dateValid, timeToValid) {
if (!dateValid || !timeToValid) return "—";
const now = new Date(this.now);
const target = new Date(`${dateValid}T${timeToValid}`);
const diffMs = target - now;
timeLeft3(dateValidFrom,dateValidTo, timeValidFrom, timeToValid) {
console.log(dateValidFrom,timeValidFrom,timeValidFrom,timeToValid)
if (!dateValidFrom || !timeValidFrom || !dateValidFrom || !timeToValid) return "—";
const validDate = new Date(`${dateValidFrom}T${timeValidFrom}`);
const target = new Date(`${dateValidTo}T${timeToValid}`);
const diffMs = target - validDate;
console.log("Sisa Waktu:" + diffMs)
if (diffMs <= 0) return "Waktu habis";
const totalSeconds = Math.floor(diffMs / 1000);
@ -422,6 +433,39 @@ export default {
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
},
timeLeft(dateFrom, dateTo, timeFrom, timeTo) {
if (!dateFrom || !timeFrom || !dateTo || !timeTo) return "—";
// const now = new Date();
const start = new Date(`${dateFrom}T${timeFrom}`);
const end = new Date(`${dateTo}T${timeTo}`);
const current = this.now;
const pad = (num) => String(num).padStart(2, "0");
const diffToStart = start - current;
const diffToEnd = end - current;
// 🕰 CASE 1: Before start
if (diffToStart > 0) {
const totalSeconds = Math.floor(diffToStart / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
if (diffToEnd > 0) {
const totalSeconds = Math.floor(diffToEnd / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
// CASE 3: After end
return "❌ Waktu habis";
},
downtimeHours(from, to) {
if (!from || !to) return '-';
const [fh, fm] = from.split(':').map(Number);
@ -464,7 +508,7 @@ export default {
}
if (mission.userStatus === 'completed') return;
if (mission.userStatus === 'not_started') {
this.startMission(mission);
// this.startMission(mission);
}
if (mission.task === 'scan-qr') {
console.log(mission)
@ -483,9 +527,13 @@ export default {
},
async startMission(mission) {
if (!mission || !mission.id) {
console.error("Invalid mission", mission);
return;
}
await createMissionLog({
mission: mission.id,
user_id: this.currentUser.id,
user_id: this.currentUser?.value?.id,
status: 'in_progress',
coin: mission.coin,
point: mission.point
@ -505,10 +553,15 @@ export default {
beforeUnmount() {
clearInterval(this.timer);
},
computed: {
currentUser() {
return this.authStore.currentUser;
}
}
};
</script>
<script setup>
import { useAuthStore } from '@/stores/auth'
// import { useAuthStore } from '@/stores/auth'
import { useRouter } from 'vue-router'
const router = useRouter()
const auth = useAuthStore()

View File

@ -1,4 +1,17 @@
<template>
<!-- Header Section -->
<div class="w-full px-6 py-4 flex items-center justify-between backdrop-blur-md bg-white/30 border-b border-emerald-100 z-20">
<!-- Back Button -->
<button
@click="goBack"
class="flex items-center gap-2 text-emerald-600 hover:text-emerald-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
<span class="font-semibold">Kembali</span>
</button>
</div>
<div
class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-b from-teal-100 to-lime-200 p-6"
>
@ -111,8 +124,8 @@
import { QrcodeStream } from "vue-qrcode-reader"
import api from "@/util/api"
import { useRoute } from "vue-router"
import { createMissionLog } from '@/services/missions';
import { useAuthStore } from '@/stores/auth'
// import { createMissionLog } from '@/services/missions';
// import { useAuthStore } from '@/stores/auth'
const mission = ref({
id: 1,
@ -133,8 +146,12 @@
const scanning = ref(false)
const detectedOnce = ref(false)
const router = useRoute();
const authStore = useAuthStore()
const currentUser = computed(() => authStore.currentUser)
// const authStore = useAuthStore()
// const currentUser = computed(() => authStore.currentUser)
function goBack() {
window.history.back('/mission/missions');
}
onMounted(async () => {
if(router.query.mission){
@ -224,7 +241,7 @@
// data: result,
// completed_at: new Date().toISOString(),
// })
this.startMission(data={
this.startMission({
mission: mission.value.id,
user_id: user_id,
type: "scan-qr",
@ -281,18 +298,22 @@
alert("🎉 Misi selesai! Semua task telah disimpan.")
}
async function startMission(mission) {
console.log("mission: ", mission)
await createMissionLog({
mission: mission.id,
user_id: currentUser.id,
status: 'in_progress',
coin: mission.coin,
point: mission.point
});
// async function startMission(mission) {
// if (!mission || !mission.id) {
// console.error(" Invalid mission:", mission);
// return;
// }
// console.log("mission: ", mission)
// await createMissionLog({
// mission: mission.id,
// user_id: currentUser.id,
// status: 'in_progress',
// coin: mission.coin,
// point: mission.point
// });
mission.userStatus = 'in_progress';
}
// mission.userStatus = 'in_progress';
// }
</script>
<style scoped>

View File

@ -28,7 +28,7 @@ const routes = [
})
},
{ path: '/entertainment/manga/:manga_id/chapters/:chapter_id/', name:'manga-list', component:ChapterListPage, props:true},
{ path: '/entertainment/manga/:manga_id/chapters/:chapter_id/pages', name:'manga-read', component:ChapterListPage, props:true},
{ path:'/mission/quest/:id/missions', name: 'quest-missions', component:MissionPage}
]

View File

@ -10,3 +10,7 @@ export const getChapters = async(id) => {
export const getMangaByChapter = async(manga_id, chapter_id) => {
return await api.get(`/entertainment/manga/${manga_id}/chapters/${chapter_id}/`).then((res) => res.data);
};
export const getMangaByChapterPages = async (manga_id, chapter_id) =>{
return await api.get(`/entertainment/manga/${manga_id}/chapters/${chapter_id}/pages`).then((res) => res.data);
}