-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
공태현
committed
Feb 20, 2024
1 parent
df540d0
commit 48e6e55
Showing
11 changed files
with
867 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { supabase } from "../supabaseClient"; | ||
|
||
class ProgramRepository { | ||
async fetchTotalProcutCount() { | ||
const { count, error } = await supabase.from("program").select("*", { count: "exact" }); | ||
|
||
if (error) throw new Error(error.message); | ||
return count; | ||
} | ||
|
||
async fetchProgramById({ programId }) { | ||
if (programId === "undefined") return { program: {} }; | ||
|
||
const { data: program, error } = await supabase | ||
.from("program") | ||
.select("*, product(*), act(*)") | ||
.eq("id", programId) | ||
.order("createdAt", { | ||
foreignTable: "act", | ||
ascending: true, | ||
}) | ||
.maybeSingle(); | ||
|
||
if (error) throw new Error(error.message); | ||
|
||
return { program }; | ||
} | ||
|
||
async fetchProgramByPaging({ pageNum = 1 }) { | ||
let page = (Number(pageNum) - 1) * 20; | ||
|
||
if (page < 0) page = 0; | ||
|
||
const { data: programs, error } = await supabase | ||
.from("program") | ||
.select("*") | ||
.order("createdAt", { ascending: true }) | ||
.range(page, page + 19); | ||
|
||
if (error) throw new Error(error.message); | ||
|
||
return { programs }; | ||
} | ||
|
||
async deleteProgramById({ programId }) { | ||
const { error } = await supabase.from("program").delete().in("id", programId); | ||
if (error) throw new Error(error.message); | ||
} | ||
} | ||
|
||
export default new ProgramRepository(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import programRepository from "../../lib/repository/programRepository.js"; | ||
|
||
export const load = async ({ url }) => { | ||
const pageNum = url.searchParams.get("pageNum") || 1; | ||
|
||
const { programs } = await programRepository.fetchProgramByPaging({ pageNum }); | ||
const totalProgramCount = await programRepository.fetchTotalProcutCount(); | ||
|
||
return { programs, totalProgramCount }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<script> | ||
import { enhance } from "$app/forms"; | ||
import { goto } from "$app/navigation"; | ||
import { page } from "$app/stores"; | ||
export let data; | ||
const { programs, totalProgramCount } = data; | ||
let selectedPrograms = []; | ||
$: pageNum = $page.url.searchParams.get("pageNum") || 1; | ||
let isAllSelected = false; | ||
const toggleProgramSelection = (idx) => { | ||
if (selectedPrograms.includes(idx)) { | ||
selectedPrograms = selectedPrograms.filter((selectedIdx) => selectedIdx !== idx); | ||
} else { | ||
selectedPrograms = [...selectedPrograms, idx]; | ||
} | ||
}; | ||
function toggleSelectAll() { | ||
if (!isAllSelected) { | ||
selectedPrograms = programs; | ||
isAllSelected = true; | ||
} else { | ||
selectedPrograms = []; | ||
isAllSelected = false; | ||
} | ||
} | ||
const handleDelete = async ({ formData }) => { | ||
if (selectedPrograms.length === 0) { | ||
alert("삭제할 프로그램을 선택해주세요"); | ||
return; | ||
} | ||
const response = await fetch("/program", { | ||
method: "DELETE", | ||
body: JSON.stringify(selectedPrograms), | ||
headers: { | ||
"content-type": "application/json", | ||
}, | ||
}); | ||
let result = await response.json(); | ||
if (result) { | ||
alert("삭제되었습니다."); | ||
window.location.reload(); | ||
} | ||
}; | ||
</script> | ||
|
||
<div class="relative overflow-x-auto shadow-md sm:rounded-lg"> | ||
<table class="h-full w-full table-fixed text-left text-sm text-gray-500 rtl:text-right dark:text-gray-400"> | ||
<thead class="bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700 dark:text-gray-400"> | ||
<tr> | ||
<th scope="col" class="p-4"> | ||
<div class="flex items-center"> | ||
<input | ||
id="checkbox-all-search" | ||
checked={isAllSelected} | ||
on:change={toggleSelectAll} | ||
type="checkbox" | ||
class="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600 dark:focus:ring-offset-gray-800" /> | ||
<label for="checkbox-all-search" class="sr-only">checkbox</label> | ||
</div> | ||
</th> | ||
<th scope="col" class="px-6 py-3"> Index </th> | ||
<th scope="col" class="px-6 py-3"> Image </th> | ||
<th scope="col" class="px-6 py-3"> Name </th> | ||
<th scope="col" class="px-6 py-3"> InfoText </th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{#each programs as program, idx} | ||
<tr class="border-b bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-600"> | ||
<td class="w-4 p-4"> | ||
<div class="flex items-center"> | ||
<input | ||
on:click={() => toggleProgramSelection(program)} | ||
checked={selectedPrograms.some((selectedProgram) => selectedProgram.id === program.id)} | ||
type="checkbox" | ||
class="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600 dark:focus:ring-offset-gray-800" /> | ||
<label for="checkbox-table-search-1" class="sr-only">checkbox</label> | ||
</div> | ||
</td> | ||
<td class="px-6 py-4"> {idx + 1} </td> | ||
<th | ||
scope="row" | ||
on:click={() => goto(`/program/${program.id}`)} | ||
class="flex items-center whitespace-nowrap py-4 text-gray-900 dark:text-white"> | ||
<img class="object-fit h-20 w-20" src={program.bannerImageUrl} alt="product" /> | ||
</th> | ||
<td on:click={() => goto(`/program/${program.id}`)} class="px-6 py-4"> {program.name} </td> | ||
<td on:click={() => goto(`/program/${program.id}`)} class="px-6 py-4"> | ||
<div class="flex items-center"> | ||
{program.infoText} | ||
</div> | ||
</td> | ||
</tr> | ||
{/each} | ||
</tbody> | ||
</table> | ||
|
||
{#if totalProgramCount > 0 && !isNaN(Number(pageNum))} | ||
<div class="my-3 flex w-full justify-center gap-4"> | ||
<button | ||
type="button" | ||
class="mr-4 cursor-pointer" | ||
disabled={Number(pageNum) === 1} | ||
on:click={() => { | ||
window.location.href = `/program?pageNum=1`; | ||
}}>«</button> | ||
{#if Number(pageNum) !== 1} | ||
<button | ||
type="button" | ||
on:click={() => { | ||
window.location.href = `/program?pageNum=${Number(pageNum) - 1}`; | ||
}}>{Number(pageNum) - 1}</button> | ||
{/if} | ||
<button class="font-bold text-primary underline">{Number(pageNum)}</button> | ||
{#if Number(pageNum) < Math.ceil(totalProgramCount / 20)} | ||
<button | ||
type="button" | ||
on:click={() => { | ||
window.location.href = `/program?pageNum=${Number(pageNum) + 1}`; | ||
}}>{Number(pageNum) + 1}</button> | ||
{/if} | ||
<button | ||
class="ml-4 cursor-pointer" | ||
disabled={Number(pageNum) >= Math.ceil(totalProgramCount / 20)} | ||
on:click={() => { | ||
window.location.href = `/program?pageNum=${Math.ceil(totalProgramCount / 20)}`; | ||
}}>»</button> | ||
</div> | ||
{/if} | ||
|
||
<div class="mt-4 flex items-center justify-center"> | ||
<button | ||
on:click={() => goto("/program/null")} | ||
type="button" | ||
class="mb-2 me-2 rounded-lg border border-gray-300 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:hover:border-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-700" | ||
>프로그램 추가</button> | ||
<form method="post" use:enhance={handleDelete} action="?/delete"> | ||
<button | ||
type="submit" | ||
formaction="?/delete" | ||
class="mb-2 me-2 rounded-lg border border-gray-300 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:hover:border-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-700" | ||
>선택한 프로그램 삭제</button> | ||
</form> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { json } from "@sveltejs/kit"; | ||
import programRepository from "../../lib/repository/programRepository.js"; | ||
|
||
export const DELETE = async ({ request }) => { | ||
try { | ||
const programs = await request.json(); | ||
|
||
const programIds = programs.map((err) => err.id); | ||
|
||
await programRepository.deleteProgramById({ programId: programIds }); | ||
|
||
return json(true); | ||
} catch (err) { | ||
console.error("product/server.js Error : ", err); | ||
return json(false); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import programRepository from "../../../lib/repository/programRepository.js"; | ||
import { supabase } from "../../../lib/supabaseClient.js"; | ||
|
||
export const load = async ({ params }) => { | ||
const programId = params.programId; | ||
|
||
if (programId === "null") { | ||
const program = {}; | ||
return { program }; | ||
} | ||
|
||
const { program } = await programRepository.fetchProgramById({ programId }); | ||
|
||
return { program }; | ||
}; | ||
|
||
export const actions = { | ||
save: async ({ request, params }) => { | ||
const product = await request.formData(); | ||
|
||
// 기본 정보. | ||
const basicInfo = { | ||
name: product.get("name"), | ||
isComingSoon: product.get("isComingSoon"), | ||
duration: product.get("duration"), | ||
feature: product.get("feature"), | ||
infoText: product.get("infoText"), | ||
ingredient: product.get("ingredient"), | ||
manual: product.get("manual"), | ||
}; | ||
|
||
if (product.get("bannerImage") === "no") basicInfo.bannerImageUrl = null; | ||
else if (product.get("bannerImage") === "url") { | ||
basicInfo.bannerImageUrl = product.get("bannerImageUrl"); | ||
} else { | ||
const file = product.get("bannerImageFile"); | ||
// 파일 등록 후 url. 등록. | ||
const { data, error } = await supabase.storage | ||
.from("program-images") | ||
.upload(`img/${params.programId}/banner/${file.name}`, file, { | ||
cacheControl: "3600", | ||
upsert: true, | ||
}); | ||
|
||
let { data: imageUrl } = await supabase.storage.from("program-images").getPublicUrl(data.path); | ||
imageUrl = imageUrl.publicUrl; | ||
basicInfo.bannerImageUrl = imageUrl; | ||
} | ||
|
||
if (product.get("productImage") === "no") basicInfo.productImageUrl = null; | ||
else if (product.get("productImage") === "url") { | ||
basicInfo.productImageUrl = product.get("productImageUrl"); | ||
} else { | ||
const file = product.get("productImageFile"); | ||
const { data, error } = await supabase.storage | ||
.from("program-images") | ||
.upload(`img/${params.programId}/product/${file.name}`, file, { | ||
cacheControl: "3600", | ||
upsert: true, | ||
}); | ||
let { data: imageUrl } = await supabase.storage.from("program-images").getPublicUrl(data.path); | ||
imageUrl = imageUrl.publicUrl; | ||
basicInfo.productImageUrl = imageUrl; | ||
} | ||
|
||
if (product.get("bannerComingSoonImage") === "no") basicInfo.bannerImageUrlComingSoon = null; | ||
else if (product.get("bannerComingSoonImage") == "url") { | ||
basicInfo.bannerImageUrlComingSoon = product.get("bannerComingSoonImageUrl"); | ||
} else { | ||
const file = product.get("bannerComingSoonImageFile"); | ||
const { data, error } = await supabase.storage | ||
.from("program-images") | ||
.upload(`img/${params.programId}/bannerComingSoon/${file.name}`, file, { | ||
cacheControl: "3600", | ||
upsert: true, | ||
}); | ||
|
||
let { data: imageUrl } = await supabase.storage.from("program-images").getPublicUrl(data.path); | ||
imageUrl = imageUrl.publicUrl; | ||
basicInfo.bannerImageUrlComingSoon = imageUrl; | ||
} | ||
|
||
if (params.programId === "null") { | ||
const { error } = await supabase.from("program").insert({ ...basicInfo }); | ||
|
||
if (error) { | ||
console.error("Program Save(Insert) Err", error.message); | ||
return "Fail"; | ||
} | ||
|
||
return "Success"; | ||
} | ||
const { error } = await supabase | ||
.from("program") | ||
.update({ ...basicInfo }) | ||
.eq("id", params.programId); | ||
|
||
if (error) { | ||
console.error("Program Save Err", error.message); | ||
return "Fail"; | ||
} | ||
|
||
return "Success"; | ||
}, | ||
}; |
Oops, something went wrong.