Skip to content

Commit

Permalink
Deploy Thome
Browse files Browse the repository at this point in the history
  • Loading branch information
공태현 committed Feb 20, 2024
1 parent df540d0 commit 48e6e55
Show file tree
Hide file tree
Showing 11 changed files with 867 additions and 1 deletion.
51 changes: 51 additions & 0 deletions src/lib/repository/programRepository.js
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();
3 changes: 3 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<a
href="/product"
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">상품</a>
<a
href="/program"
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">프로그램</a>
<a
href="/coupon"
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">쿠폰</a>
Expand Down
10 changes: 10 additions & 0 deletions src/routes/program/+page.server.js
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 };
};
152 changes: 152 additions & 0 deletions src/routes/program/+page.svelte
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>
17 changes: 17 additions & 0 deletions src/routes/program/+server.js
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);
}
};
105 changes: 105 additions & 0 deletions src/routes/program/[programId]/+page.server.js
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";
},
};
Loading

0 comments on commit 48e6e55

Please sign in to comment.