Skip to content

Commit

Permalink
feat: 가게 연락처 조회 수정 추가 삭제 페이지 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
tlarbals824 committed Nov 2, 2024
1 parent 37bc97d commit 2126555
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.clip.admin

import com.clip.admin.dto.CreateContactRequest
import com.clip.admin.dto.GetStoreContactInfoResponse
import com.clip.admin.dto.UpdateContactRequest
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
import java.util.UUID

@Controller
@RequestMapping("/store/{id}/contact")
class StoreContactController {

@GetMapping
@ResponseBody
fun getStoreContact(
@PathVariable id: String
): List<GetStoreContactInfoResponse> {
return listOf(
GetStoreContactInfoResponse(UUID.randomUUID().toString(),"PHONE", "010-1234-5678"),
GetStoreContactInfoResponse(UUID.randomUUID().toString(),"INSTAGRAM", "@store1"),
)
}

@GetMapping("/type")
@ResponseBody
fun getStoreContactType(): List<String> {
return listOf("PHONE", "INSTAGRAM")
}

@GetMapping("/{contactId}")
@ResponseBody
fun getStoreContactDetail(
@PathVariable id: String,
@PathVariable contactId: String
): GetStoreContactInfoResponse {
return GetStoreContactInfoResponse(UUID.randomUUID().toString(),"PHONE", "010-1234-5678")
}

@PostMapping
@ResponseBody
fun createStoreContact(
@PathVariable id: String,
@RequestBody request: CreateContactRequest
) {
println("create contact: $request")
}

@PutMapping("/{contactId}")
@ResponseBody
fun updateStoreContact(
@PathVariable id: String,
@PathVariable contactId: String,
@RequestBody request: UpdateContactRequest
) {
println("update contact: $request")
}

@DeleteMapping("/{contactId}")
@ResponseBody
fun deleteStoreContact(
@PathVariable id: String,
@PathVariable contactId: String
) {
println("delete contact: $contactId")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class StoreController {
return GetStoreDetailInfoResponse(
UUID.randomUUID().toString(),
"store1",
"https://store1.com",
"https://images.unsplash.com/photo-1529927066849-79b791a69825?q=80&w=3569&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"store1 introduction",
true,
37.123456,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.clip.admin.dto

data class CreateContactRequest(
val contactType: String,
val value: String
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.clip.admin.dto

data class GetStoreContactInfoResponse(
val id: String,
val contactType: String,
val value: String
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.clip.admin.dto

data class UpdateContactRequest(
val contactType: String,
val value: String
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ const StoreUpdate = {

// 가게 메뉴 조회
StoreMenu.init(storeData.id);
// 가게 연락처 조회
StoreContact.init(storeData.id);


this.updateImagePreview(storeData.imgUrl);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const StoreContactApi = {

endpoints: {
list: (storeId) => `/store/${storeId}/contact`,
create: (storeId) => `/store/${storeId}/contact`,
detail: (storeId, contactId) => `/store/${storeId}/contact/${contactId}`,
update: (storeId, contactId) => `/store/${storeId}/contact/${contactId}`,
delete: (storeId, contactId) => `/store/${storeId}/contact/${contactId}`,
types: (storeId) => `/store/${storeId}/contact/type`
},

async getContacts(storeId) {
const response = await fetch(this.endpoints.list(storeId));
if (!response.ok) throw new Error('연락처 목록을 불러오는데 실패했습니다.');
return await response.json();
},

async getContact(storeId, contactId) {
const response = await fetch(this.endpoints.detail(storeId, contactId));
if (!response.ok) throw new Error('연락처 정보를 불러오는데 실패했습니다.');
return await response.json();
},

async createContact(storeId, contactData) {
const response = await fetch(this.endpoints.create(storeId), {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(contactData)
});
if (!response.ok) throw new Error('연락처 추가에 실패했습니다.');
},

async updateContact(storeId, contactId, contactData) {
const response = await fetch(this.endpoints.update(storeId, contactId), {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(contactData)
});
if (!response.ok) throw new Error('연락처 수정에 실패했습니다.');
},

async deleteContact(storeId, contactId) {
const response = await fetch(this.endpoints.delete(storeId, contactId), {
method: 'DELETE'
});
if (!response.ok) throw new Error('연락처 삭제에 실패했습니다.');
return true;
},

async getContactTypes(storeId) {
const response = await fetch(this.endpoints.types(storeId));
if (!response.ok) throw new Error('연락처 유형을 불러오는데 실패했습니다.');
return await response.json();
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const StoreContact = {
contactModal: null,
currentContactId: null,
currentStoreId: null,
contactTypes: [], // 연락처 타입 저장

init(storeId) {
this.contactModal = new bootstrap.Modal(document.getElementById('contactModal'));
this.currentStoreId = storeId;
this.loadContactTypes(storeId).then(() => {
this.loadContacts();
this.initializeEvents();
});
},

initializeEvents() {

document.getElementById('contactForm').addEventListener('submit', (e) => {
e.preventDefault();
this.saveContact();
});

document.getElementById('contactModal').addEventListener('hidden.bs.modal', () => {
document.getElementById('contactForm').reset();
this.currentContactId = null;
});
},

async loadContacts() {
try {
const contacts = await StoreContactApi.getContacts(this.currentStoreId);
this.renderContactTable(contacts);
} catch (error) {
console.error('Error:', error);
this.showError('연락처 목록을 불러오는데 실패했습니다.');
}
},

// 연락처 타입 로드
async loadContactTypes(storeId) {
try {
this.contactTypes = await StoreContactApi.getContactTypes(storeId);
this.renderContactTypes();
} catch (error) {
console.error('Error:', error);
this.showError('연락처 타입을 불러오는데 실패했습니다.');
}
},

renderContactTable(contacts) {
const tableBody = document.getElementById('contactTableBody');
tableBody.innerHTML = '';

if (contacts.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="3" class="text-center">등록된 연락처가 없습니다.</td>
</tr>
`;
return;
}

contacts.forEach(contact => {
const row = document.createElement('tr');
row.innerHTML = `
<td>
<span class="badge bg-secondary">${this.formatContactType(contact.contactType)}</span>
</td>
<td>${contact.value}</td>
<td>
<button class="btn btn-sm btn-outline-primary me-1"
onclick="StoreContact.openEditModal('${contact.id}')">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger"
onclick="StoreContact.deleteContact('${contact.id}')">
<i class="bi bi-trash"></i>
</button>
</td>
`;
tableBody.appendChild(row);
});
},


// 연락처 타입 드롭다운 렌더링
renderContactTypes() {
const select = document.getElementById('contactType');
select.innerHTML = '<option value="">선택하세요</option>';

this.contactTypes.forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = this.formatContactType(type);
select.appendChild(option);
});
},

// 연락처 타입 포맷팅
formatContactType(type) {
const typeMap = {
'PHONE': '전화번호',
'INSTAGRAM': '인스타그램'
};
return typeMap[type] || type;
},

openAddModal() {
this.currentContactId = null;
document.getElementById('contactModalLabel').textContent = '메뉴 추가';
document.getElementById('contactForm').reset();
this.contactModal.show();
},

async openEditModal(contactId) {
try {
const contact = await StoreContactApi.getContact(this.currentStoreId, contactId);
this.currentContactId = contactId;
document.getElementById('contactModalLabel').textContent = '연락처 수정';
document.getElementById('contactType').value = contact.contactType;
document.getElementById('contactValue').value = contact.value;
this.contactModal.show();
} catch (error) {
console.error('Error:', error);
this.showError('연락처 정보를 불러오는데 실패했습니다.');
}
},


async saveContact() {
if (!this.validateContactForm()) return;

const contactData = {
contactType: document.getElementById('contactType').value,
value: document.getElementById('contactValue').value
}

try {
if (this.currentContactId) {
await StoreContactApi.updateContact(this.currentStoreId, this.currentContactId, contactData);
} else {
await StoreContactApi.createContact(this.currentStoreId, contactData);
}
this.loadContacts();
this.showSuccess(this.currentContactId ? '연락처가 수정되었습니다.' : '연락처가 추가되었습니다.');
this.contactModal.hide();
} catch (error) {
console.error('Error:', error);
this.showError('연락처 저장에 실패했습니다.');
}
},

async deleteContact(contactId) {
if (!confirm('정말로 삭제하시겠습니까?')) return;

try {
await StoreContactApi.deleteContact(this.currentStoreId, contactId);
this.loadContacts();
this.showSuccess('연락처가 삭제되었습니다.');
} catch (error) {
console.error('Error:', error);
this.showError('연락처 삭제에 실패했습니다.');
}
},


validateContactForm() {
const type = document.getElementById('contactType').value;
const value = document.getElementById('contactValue').value;
if (!type || !value) {
this.showError('연락처 유형과 값을 모두 입력해주세요.');
return false;
}
return true;
},

showError(message) {
alert(message);
},

showSuccess(message) {
alert(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="contactModal">
<div class="modal fade" id="contactModal" tabindex="-1" aria-labelledby="contactModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="contactModalLabel">연락처 추가/수정</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="contactForm">
<input type="hidden" id="contactId">
<div class="mb-3">
<label for="contactType" class="form-label">종류</label>
<select class="form-select" id="contactType" required>
<option value="">선택하세요</option>
<!-- 옵션들이 동적으로 추가됨 -->
</select>
</div>
<div class="mb-3">
<label for="contactValue" class="form-label">정보(번호, 주소)</label>
<input type="text" class="form-control" id="contactValue" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
<button type="button" class="btn btn-primary" onclick="StoreContact.saveContact()">저장</button>
</div>
</div>
</div>
</div>
</th:block>
</html>
Loading

0 comments on commit 2126555

Please sign in to comment.