diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 000000000..18bd3657b --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "admin-acaf2" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..dbb58ffbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env diff --git a/404.html b/404.html new file mode 100644 index 000000000..829eda8fd --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + + + + + Page Not Found + + + + +
+

404

+

Page Not Found

+

The specified file was not found on this website. Please check the URL for mistakes and try again.

+

Why am I seeing this?

+

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

+
+ + diff --git a/README.md b/README.md deleted file mode 100644 index a29ee163b..000000000 --- a/README.md +++ /dev/null @@ -1,49 +0,0 @@ - -# :camera: 직원 사진 관리 서비스 - -직원들의 사진을 관리할 수 있는 사진 관리자 서비스를 만들어 보세요. - -과제 수행 및 리뷰 기간은 별도 공지를 참고하세요! -## [과제 수행 및 제출 방법] -1. 현재 저장소를 로컬에 클론(Clone)합니다. -2. 자신의 본명으로 브랜치를 생성합니다.(구분 가능하도록 본명을 꼭 파스칼케이스로 표시하세요, git branch KDT0_이름) -3. 자신의 본명 브랜치에서 과제를 수행합니다. -4. 과제 수행이 완료되면, 자신의 본명 브랜치를 원격 저장소에 푸시(Push)합니다.(main 브랜치에 푸시하지 않도록 꼭 주의하세요, git push origin KDT0_이름) -5. 저장소에서 main 브랜치를 대상으로 Pull Request 생성하면, 과제 제출이 완료됩니다!(E.g, main <== KDT0_이름) -6. Pull Request 링크를 LMS로도 제출해 주셔야 합니다. -7. main 혹은 다른 사람의 브랜치로 절대 병합하지 않도록 주의하세요! -8. Pull Request에서 보이는 설명을 다른 사람들이 이해하기 쉽도록 꼼꼼하게 작성하세요! -9. Pull Request에서 과제 제출 후 절대 병합(Merge)하지 않도록 주의하세요! -10. 과제 수행 및 제출 과정에서 문제가 발생한 경우, 바로 담당 멘토나 강사에서 얘기하세요! - -## [필수 요구사항] -- “AWS S3 / Firebase 같은 서비스”를 이용하여 사진을 관리할 수 있는 페이지를 구현하세요. -- 프로필 페이지를 개발하세요. -- 스크롤이 가능한 형태의 리스팅 페이지를 개발하세요. -- 전체 페이지 데스크탑-모바일 반응형 페이지를 개발하세요. -- 사진을 등록, 수정, 삭제가 가능해야 합니다. -- 유저 플로우를 제작하여 리드미에 추가하세요. -* CSS - * 애니메이션 구현 - * 상대수치 사용(rem, em) -* JavaScript - * DOM event 조작 - -## [선택 요구사항] -- 사진 관리 페이지와 관련된 기타 기능도 고려해 보세요. -- 페이지가 보여지기 전에 로딩 애니메이션이 보이도록 만들어보세요. -- 직원을 등록, 수정, 삭제가 가능하게 해보세요. -- 직원 검색 기능을 추가해 보세요. -- infinity scroll 기능을 추가해 보세요. -- 사진을 편집할 수 있는 기능을 추가해 보세요. -- LocalStorage 사용 - -## [화면 예시] -![Untitled (1)](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/38754963/5dda6755-2501-4af4-bc3e-b63a353c44c2) - -![Untitled (2)](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/38754963/6c1805f1-2b00-453e-a729-2b483612726d) - -## [흐름] -![Untitled](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/38754963/e2934c05-26f6-4ef6-88d4-beed76aa007a) - - diff --git a/css/main.css b/css/main.css new file mode 100644 index 000000000..6e26d79a8 --- /dev/null +++ b/css/main.css @@ -0,0 +1,446 @@ +:root { + --text-color: #fff; + --background-color: #fff; + --accent-color: #fff; + --modal-box-width: 600px; +} + +html { + width: 70vw; + height: 70vw; + margin: auto; +} + +* { + font-family: "Noto Sans KR", sans-serif; +} + +body { + font-family: "Noto Sans KR", sans-serif; + min-width: 20rem; + background-color: black; + display: grid; + grid-template-columns: 100px 5fr; + grid-template-rows: 100px auto 50px; + grid-template-areas: + "header header" + "aside main" + "footer footer"; +} + +body.masked > *:not(main, aside) { + opacity: 0.2; + pointer-events: none; /* 클릭 또는 터치 이벤트 무시 */ + user-select: none; /* 선택 불가능하게 설정 */ +} + +header { + grid-area: header; + background-color: black; +} + +aside { + grid-area: aside; + background-color: wheat; +} + +main { + grid-area: main; + background-color: darkgray; +} + +footer { + grid-area: footer; + background-color: gray; +} + +a { + text-decoration: none; + white-space: nowrap; + color: #fff; +} + +li { + white-space: nowrap; +} + +.navbar { + display: flex; + align-items: center; + height: 100%; +} + +.navbar__logo { + display: flex; + font-size: 24px; + color: #fff; + width: 100%; +} + +.navbar__logo i { + color: orange; + font-size: 40px; + padding-right: 10px; +} + +.navbar__menu { + display: flex; + list-style: none; + padding-right: 50px; +} + +.navbar__menu li { + border-radius: 4px; + padding: 8px 12px; +} + +.navbar__menu li:hover { + background-color: orange; + transition: background-color 0.5s ease; +} + +.navbar__tooglebtn { + display: none; + font-size: 24px; + color: orange; + margin-left: auto; +} + +.staff-box__modal { + transform: scale(1); + transition: opacity 0.3s, transform 0.3s; +} + +.staff-box { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + height: 100%; + width: 100%; +} + +.staff-box__item { + height: 20rem; + width: 20rem; + overflow: hidden; + margin: 1rem; + border-radius: 4%; + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #f8f9fa; + box-shadow: rgba(0, 0, 0, 0.04) 0px 4px 16px 0px; +} + +.staff-box__item:hover { + transform: translateY(-10px); + transition: box-shadow 0.25s ease-in 0s, transform 0.25s ease-in 0s; +} + +.staff-box__item-id { + display: flex; + background-color: skyblue; +} + +.staff-box__item-id div { + margin-left: auto; + padding: 10px 10px 5px 0; +} + +.staff-box__item-img { + display: flex; + overflow: hidden; +} + +.staff-box__item-img img { + width: 180px; + border-radius: 50%; + margin: auto; +} + +.staff-box__item-detail { + display: flex; + background-color: skyblue; +} + +.staff-box__item-detail ul { + padding: 5px 0 5px 10px; +} + +.staff-box__item-detail li { + margin-bottom: 5px; +} + +@media screen and (max-width: 1148px) { + /* .navbar { + height: auto; + flex-direction: column; + align-items: flex-start; + justify-content: space-around; + padding: 30px 0px; + background-color: black; + } + + .navbar__menu { + display: none; + flex-direction: column; + align-items: center; + width: 100%; + margin: auto; + } + + .navbar__menu li { + text-align: center; + width: 100%; + } + + .navbar__tooglebtn { + display: block; + } + + .navbar__menu.active { + display: flex; + } */ + + .staff-box__item { + width: 27rem; + } + html { + width: 100vw; + } +} + +.modal { + opacity: 0; + visibility: hidden; + transform: scale(0.5); + transition: opacity 0.3s, transform 0.3s; + /* transform: translateY(-50%); + transition: opacity 0.3s, visibility 0.3s, transform 0.3s; */ +} + +.modal.active { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1; + opacity: 1; + visibility: visible; + transform: scale(1); + /* transform: translateY(0); */ +} + +.modal-box { + display: none; +} + +.modal-box.active { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: var(--modal-box-width); + height: calc(4.5 / 3.5 * var(--modal-box-width)); + background-color: #fff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-radius: 4%; + display: flex; + justify-content: space-between; + flex-direction: column; + overflow: hidden; +} + +.modal-box__nav { + display: flex; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); +} + +.modal-box__nav__submitTooglebtn { + display: none; +} + +.modal-box__nav__submitTooglebtn.active { + margin-left: auto; + display: block; +} + +.modal-box__nav__submitTooglebtn a i { + font-size: 30px; + color: black; + padding: 10px 10px 5px 0; +} + +.modal-box__nav__modifyTooglebtn { + display: none; +} + +.modal-box__nav__modifyTooglebtn.active { + margin-left: auto; + display: block; +} + +.modal-box__nav__modifyTooglebtn a i { + font-size: 30px; + color: black; + padding: 10px 10px 5px 0; +} + +.modal-box__inputInfo { + display: flex; + padding: 10px 0 5px 20px; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.modal-box__inputInfo div { + margin-top: 10px; + margin-bottom: 10px; +} + +.modal-box__inputInfo div input, +.modal-box__inputInfo select { + border-radius: 4px; +} + +.submitBtn { + display: none; +} + +.submitBtn.active { + display: block; + width: auto; + height: 100px; +} + +.submitButton { + display: none; +} + +.submitButton.active { + display: block; + width: 100%; + height: 100%; + font-size: 20px; + margin: auto; + background-color: #f8f9fa; + border: none; +} + +.submitButton.active:hover { + transition: background-color 0.5s ease; + background-color: orange; +} + +.modifyBtn { + display: none; +} + +.modifyBtn.active { + display: block; + width: auto; + height: 100px; +} + +.modifyButton { + display: none; +} + +.modifyButton.active { + display: block; + width: 100%; + height: 101%; + font-size: 20px; + margin: auto; + background-color: #f8f9fa; + border: none; +} + +.modifyButton.active:hover { + transition: background-color 0.5s ease; + background-color: orange; +} + +.modal-box__inputImg { + display: flex; + overflow: auto; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + background-color: #f8f9fa; +} + +.modal-box__inputImg img { + border-radius: 50%; + width: auto; + height: 100%; +} + +.preview-image { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +} + +.staff-box__item.active { + box-shadow: rgba(0, 255, 0, 1) 0px 4px 16px 0px; +} + +.staff-box__item.active.select { + box-shadow: rgba(255, 0, 0, 1) 0px 4px 16px 0px; +} + +.staff-box__delete { + display: none; +} + +.staff-box__delete.active { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; + background-color: rgba(0, 255, 0, 1); +} + +.staff-box__delete.active:hover { + background-color: rgba(255, 0, 0, 1); + transition: background-color 0.5s ease; +} + +.staff-box__delete.active i { + font-size: 100px; + color: #fff; + padding: 20px 6px; +} + +.staff-box__item.modify { + box-shadow: orange 0px 4px 16px 0px; +} + +.staff-box__modify { + display: none; +} + +.staff-box__modify.active { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; + background-color: gray; +} + +.staff-box__modify.active:hover { + background-color: orange; + transition: background-color 0.5s ease; +} + +.staff-box__modify.active i { + font-size: 100px; + color: #fff; + padding: 20px 6px; +} diff --git a/css/reset.css b/css/reset.css new file mode 100644 index 000000000..82935aece --- /dev/null +++ b/css/reset.css @@ -0,0 +1,124 @@ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/firebase.json b/firebase.json new file mode 100644 index 000000000..6cf82049c --- /dev/null +++ b/firebase.json @@ -0,0 +1,33 @@ +{ + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + }, + "storage": { + "rules": "storage.rules" + }, + "functions": [ + { + "source": "functions", + "codebase": "default" + }, + { + "source": "hojin", + "codebase": "hojin", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ] + } + ] +} diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 000000000..415027e5d --- /dev/null +++ b/firestore.indexes.json @@ -0,0 +1,4 @@ +{ + "indexes": [], + "fieldOverrides": [] +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 000000000..cd1a4346f --- /dev/null +++ b/firestore.rules @@ -0,0 +1,9 @@ +rules_version = '2'; + +service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + allow read, write: if false; + } + } +} \ No newline at end of file diff --git a/hojin/.gitignore b/hojin/.gitignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/hojin/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/hojin/index.js b/hojin/index.js new file mode 100644 index 000000000..e81477f69 --- /dev/null +++ b/hojin/index.js @@ -0,0 +1,19 @@ +/** + * Import function triggers from their respective submodules: + * + * const {onCall} = require("firebase-functions/v2/https"); + * const {onDocumentWritten} = require("firebase-functions/v2/firestore"); + * + * See a full list of supported triggers at https://firebase.google.com/docs/functions + */ + +const {onRequest} = require("firebase-functions/v2/https"); +const logger = require("firebase-functions/logger"); + +// Create and deploy your first functions +// https://firebase.google.com/docs/functions/get-started + +// exports.helloWorld = onRequest((request, response) => { +// logger.info("Hello logs!", {structuredData: true}); +// response.send("Hello from Firebase!"); +// }); diff --git a/hojin/package.json b/hojin/package.json new file mode 100644 index 000000000..392196b98 --- /dev/null +++ b/hojin/package.json @@ -0,0 +1,23 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": { + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "18" + }, + "main": "index.js", + "dependencies": { + "firebase-admin": "^11.8.0", + "firebase-functions": "^4.3.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.1.0" + }, + "private": true +} diff --git a/index.html b/index.html new file mode 100644 index 000000000..7106a6aa4 --- /dev/null +++ b/index.html @@ -0,0 +1,203 @@ + + + + + + + 직원 관리 시스템 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +
+ +
+
+ + + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/delete.js b/js/delete.js new file mode 100644 index 000000000..1d9096d9b --- /dev/null +++ b/js/delete.js @@ -0,0 +1,63 @@ +const selectedElements = document.querySelectorAll(".staff-box__item.active.select"); +const selectedEmployeeIDs = [...selectedElements].map((item) => item.getAttribute("data-id")); + +function toggleClassOnElement(element, className) { + element?.classList.toggle(className); +} + +document.body.addEventListener("click", function (event) { + const clickedItem = event.target.closest(".staff-box__item"); + if (!clickedItem || !clickedItem.classList.contains("active")) return; + + toggleClassOnElement(clickedItem, "select"); +}); + +document.querySelector(".employee-delete").addEventListener("click", function () { + [...document.querySelectorAll(".staff-box__item")].forEach((item) => toggleClassOnElement(item, "active")); + toggleClassOnElement(document.querySelector("body"), "masked"); + toggleClassOnElement(document.querySelector(".staff-box__delete"), "active"); +}); + +document.querySelector(".staff-box__delete").addEventListener("click", async function () { + const selectedElements = document.querySelectorAll(".staff-box__item.active.select"); + const selectedEmployeeIDs = [...selectedElements].map((item) => item.getAttribute("data-id")); + + document.querySelectorAll(".staff-box__item.active.select").forEach((item) => item.remove()); + + await deleteDataFromFirebase(selectedEmployeeIDs); + + [...document.querySelectorAll(".staff-box__item")].forEach((item) => toggleClassOnElement(item, "active")); + toggleClassOnElement(document.querySelector("body"), "masked"); + toggleClassOnElement(document.querySelector(".staff-box__delete"), "active"); +}); + +const db = firebase.firestore(); +const storage = firebase.storage().ref(); + +async function deleteDataFromFirebase(selectedEmployeeIDs) { + try { + const employeesRef = db.collection("user"); + + for (const id of selectedEmployeeIDs) { + const employeeDoc = await employeesRef.doc(id).get(); + const employeeData = employeeDoc.data(); + + if (employeeData && employeeData.imageUrl) { + const imageUrl = employeeData.imageUrl; + const cleanUrl = imageUrl.split("?")[0]; + const imageKey = cleanUrl.split("%2F").pop(); + const imageKeyDecoded = decodeURIComponent(imageKey); + + try { + await storage.child(`image/${imageKeyDecoded}`).delete(); + } catch (error) { + console.error("Error processing imageUrl for employee:", id, error); + } + } + + await employeesRef.doc(id).delete(); + } + } catch (error) { + console.error("Error:", error); + } +} diff --git a/js/main.js b/js/main.js new file mode 100644 index 000000000..62f6e00c2 --- /dev/null +++ b/js/main.js @@ -0,0 +1,30 @@ +const toogleBtn = document.querySelector(".navbar__tooglebtn"); + +function toggleClassOnElement(element, className) { + element?.classList.toggle(className); +} + +const toggleNavbar = () => { + toggleClassOnElement(document.querySelector(".navbar__menu"), "active"); + console.log("click"); +}; + +toogleBtn.addEventListener("click", toggleNavbar); + +document.getElementById("image").addEventListener("change", function (e) { + if (e.target.files && e.target.files[0]) { + const reader = new FileReader(); + + reader.onload = function (e) { + document.querySelector(".modal-box__inputImg").innerHTML = `Image Preview`; + }; + + reader.readAsDataURL(e.target.files[0]); + } +}); + +document.addEventListener("dragstart", function (event) { + if (event.target.tagName == "IMG") { + event.preventDefault(); + } +}); diff --git a/js/modify.js b/js/modify.js new file mode 100644 index 000000000..c2b1fd4ef --- /dev/null +++ b/js/modify.js @@ -0,0 +1,196 @@ +const modal = document.querySelector(".modal"); +const modalBox = document.querySelector(".modal-box"); +const form = document.querySelector("form"); +const modifyButton = document.querySelector(".modifyButton"); + +const db = firebase.firestore(); +const storage = firebase.storage(); + +function createStaffTemplate(docData, doc) { + return ` +
+
+
${docData.employeeNumber}
+
+
+ 프로필 사진 +
+
+ +
+
+ `; +} + +function toggleClassOnElement(element, className) { + element?.classList.toggle(className); +} + +const toggleEmployeeModify = () => { + [...document.querySelectorAll(".staff-box__item")].forEach((item) => toggleClassOnElement(item, "modify")); + toggleClassOnElement(document.querySelector(".modal-box__nav__modifyTooglebtn"), "active"); + toggleClassOnElement(document.querySelector(".staff-box__modify"), "active"); + toggleClassOnElement(document.querySelector(".employee-modify"), "active"); + toggleClassOnElement(document.querySelector(".modifyButton"), "active"); + toggleClassOnElement(document.querySelector(".modifyBtn"), "active"); + toggleClassOnElement(document.querySelector("body"), "masked"); +}; + +const toggleModal = () => { + toggleClassOnElement(document.querySelector(".modal-box"), "active"); + toggleClassOnElement(document.querySelector(".modal"), "active"); +}; + +document.querySelector(".modal-box__nav__modifyTooglebtn").addEventListener("click", toggleModal); +document.querySelector(".employee-modify").addEventListener("click", toggleEmployeeModify); +document.querySelector(".staff-box__modify").addEventListener("click", toggleEmployeeModify); + +function populateModalForm(data) { + modalBox.querySelector("input[name='employeeNumber']").value = data.employeeNumber; + modalBox.querySelector("input[name='userName']").value = data.userName; + modalBox.querySelector("select[name='dept']").value = data.dept; + modalBox.querySelector("select[name='position']").value = data.position; + modalBox.querySelector("input[name='userNumber']").value = data.userNumber; + modalBox.querySelector(".modal-box__inputImg img").src = data.imageUrl; +} + +document.body.addEventListener("click", async function (event) { + const clickedItem = event.target.closest(".staff-box__item.modify"); + + if (clickedItem && clickedItem.classList.contains("modify")) { + const previouslySelected = document.querySelector(".staff-box__item.modify.select"); + if (previouslySelected) { + previouslySelected.classList.remove("select"); + } + + clickedItem.classList.add("select"); + + const selectedElement = document.querySelector(".staff-box__item.modify.select"); + const docId = selectedElement.getAttribute("data-id"); + + fetchDataFromFirebase(docId); + + toggleClassOnElement(modal, "active"); + toggleClassOnElement(modalBox, "active"); + } +}); + +function fetchDataFromFirebase(docId) { + const userRef = db.collection("user").doc(docId); + + userRef + .get() + .then((doc) => { + if (doc.exists) { + const userData = doc.data(); + + populateModalForm(userData); + } else { + console.error("No such document!"); + } + }) + .catch((error) => { + console.error("Error getting document:", error); + }); +} + +modifyButton.addEventListener("click", async function (event) { + const selectedElement = document.querySelector(".staff-box__item.modify.select"); + const docId = selectedElement.getAttribute("data-id"); + deleteImgFromFirebase([docId]); + toggleModal(); + event.preventDefault(); + + const formData = new FormData(form); + const updatedData = {}; + formData.forEach((value, key) => (updatedData[key] = value)); + + let file = document.querySelector("#image").files[0]; + if (file) { + let storageRef = storage.ref(); + let savePath = storageRef.child("image/" + file.name); + let uploadImage = savePath.put(file); + + await new Promise((resolve, reject) => { + uploadImage.on( + "state_changed", + null, + (error) => { + console.error("실패사유는", error); + reject(error); + }, + async () => { + const url = await uploadImage.snapshot.ref.getDownloadURL(); + updatedData.imageUrl = url; + resolve(); + } + ); + }); + } + + const selectedItem = document.querySelector(".staff-box__item.modify.select"); + if (selectedItem) { + const docId = selectedItem.getAttribute("data-id"); + await updateEmployeeInFirebase(docId, updatedData); + loadStaffDataForModify(); + form.reset(); + } else { + console.error("No employee item selected."); + } + selectedItem.classList.remove("select"); + [...document.querySelectorAll(".staff-box__item")].forEach((item) => toggleClassOnElement(item, "modify")); +}); + +async function updateEmployeeInFirebase(docId, data) { + try { + await db.collection("user").doc(docId).update(data); + } catch (error) { + console.error("Error updating the employee data in Firebase:", error); + } +} + +async function loadStaffDataForModify() { + try { + const staffBox = document.querySelector(".staff-box"); + staffBox.innerHTML = ""; // clear existing data + const snapshot = await db.collection("user").get(); + snapshot.forEach((doc) => { + const staffElement = createStaffTemplate(doc.data(), doc); + staffBox.insertAdjacentHTML("beforeend", staffElement); + }); + [...document.querySelectorAll(".staff-box__item")].forEach((item) => toggleClassOnElement(item, "modify")); + } catch (error) { + console.error("Error loading staff data from Firebase:", error); + } +} + +async function deleteImgFromFirebase(selectedEmployeeIDs) { + try { + const employeesRef = db.collection("user"); + + for (const id of selectedEmployeeIDs) { + const employeeDoc = await employeesRef.doc(id).get(); + const employeeData = employeeDoc.data(); + + if (employeeData && employeeData.imageUrl) { + const imageUrl = employeeData.imageUrl; + const cleanUrl = imageUrl.split("?")[0]; + const imageKey = cleanUrl.split("%2F").pop(); + const imageKeyDecoded = decodeURIComponent(imageKey); + console.log(imageKeyDecoded); + + try { + await storage.ref().child(`image/${imageKeyDecoded}`).delete(); + } catch (error) { + console.error("Error deleting imageUrl for employee:", id, error); + } + } + } + } catch (error) { + console.error("Error in deleteImgFromFirebase:", error); + } +} diff --git a/js/register.js b/js/register.js new file mode 100644 index 000000000..d8dddd2e6 --- /dev/null +++ b/js/register.js @@ -0,0 +1,98 @@ +const form = document.querySelector("form"); +const fileInput = document.querySelector('input[type="file"]'); +const submitButton = document.querySelector(".submitButton"); +const staffBox = document.querySelector(".staff-box"); + +function toggleClassOnElement(element, className) { + element?.classList.toggle(className); +} + +const toggleEmployeeActive = () => { + toggleClassOnElement(document.querySelector(".modal"), "active"); + toggleClassOnElement(document.querySelector(".modal-box"), "active"); + toggleClassOnElement(document.querySelector(".submitButton"), "active"); + toggleClassOnElement(document.querySelector(".submitBtn"), "active"); + toggleClassOnElement(document.querySelector(".modal-box__nav__submitTooglebtn"), "active"); + form.reset(); + document.querySelector(".modal-box__inputImg").innerHTML = ""; +}; + +document.querySelector(".modal-box__nav__submitTooglebtn").addEventListener("click", toggleEmployeeActive); +document.querySelector(".employee-register").addEventListener("click", toggleEmployeeActive); + +const db = firebase.firestore(); +const storage = firebase.storage(); + +$(".submitButton").click(function (event) { + submitButton.disabled = true; + + event.preventDefault(); + + let file = document.querySelector("#image").files[0]; + let storageRef = storage.ref(); + let savePath = storageRef.child("image/" + file.name); + let uploadImage = savePath.put(file); + + uploadImage.on( + "state_changed", + null, + (error) => { + console.error("실패사유는", error); + }, + () => { + uploadImage.snapshot.ref.getDownloadURL().then((url) => { + let uploadUserdata = { + userName: $("#userName").val(), + employeeNumber: $("#employeeNumber").val(), + dept: $("#dept").val(), + position: $("#position").val(), + userNumber: $("#userNumber").val(), + imageUrl: url, + date: new Date(), + }; + + db.collection("user") + .add(uploadUserdata) + .then((docRef) => { + const staffElement = createStaffTemplate(uploadUserdata, docRef); + staffBox.insertAdjacentHTML("beforeend", staffElement); + toggleEmployeeActive(); + submitButton.disabled = false; + }) + .catch((error) => { + console.error("Error adding document: ", error); + }); + }); + } + ); +}); + +function createStaffTemplate(docData, doc) { + return ` +
+
+
${docData.employeeNumber}
+
+
+ 프로필 사진 +
+
+ +
+
+ `; +} + +async function loadStaffData() { + const snapshot = await db.collection("user").get(); + snapshot.forEach((doc) => { + const staffElement = createStaffTemplate(doc.data(), doc); + staffBox.insertAdjacentHTML("beforeend", staffElement); + }); +} + +loadStaffData(); diff --git a/readme.MD b/readme.MD new file mode 100644 index 000000000..014058925 --- /dev/null +++ b/readme.MD @@ -0,0 +1,19 @@ +# 직원 관리 시스템 + +배포주소 :https://frolicking-meerkat-ab332e.netlify.app/ + +## 직원등록 + +![직원등록](https://im.ezgif.com/tmp/ezgif-1-bca4cdb3cd.gif) + +## 직원삭제 + +![직원삭제](https://im.ezgif.com/tmp/ezgif-1-8435bf6cdb.gif) + +## 직원수정 + +![직원수정](https://im.ezgif.com/tmp/ezgif-1-6955ca35cd.gif) + +## userflow + +![userflow](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/134940630/50ad8421-5253-425e-907e-1cb93e9778f7) diff --git a/storage.rules b/storage.rules new file mode 100644 index 000000000..f08744f03 --- /dev/null +++ b/storage.rules @@ -0,0 +1,12 @@ +rules_version = '2'; + +// Craft rules based on data in your Firestore database +// allow write: if firestore.get( +// /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin; +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if false; + } + } +}