3 Commits
lab4 ... lab_4

Author SHA1 Message Date
7c901691e9 Загрузить файлы в «/» 2025-05-17 04:15:59 +04:00
53e44a3263 Загрузить файлы в «/» 2025-05-17 04:15:31 +04:00
5db5eb0e87 Загрузить файлы в «/» 2025-05-17 04:15:09 +04:00
8 changed files with 5597 additions and 0 deletions

179
controller.js Normal file
View File

@@ -0,0 +1,179 @@
import { LetterModel } from './model.js';
import { LetterView } from './view.js';
export class LetterController {
constructor(model, view) {
this.model = model;
this.view = view;
this.users = [];
this.folders = [];
this.init();
}
async init() {
try {
const [letters, users] = await Promise.all([
this.model.getAllLetters(),
this.model.getUsers()
]);
this.users = users;
this.view.renderLetters(letters, users);
this.bindEvents();
} catch (error) {
console.error('Ошибка инициализации:', error);
}
}
async loadLetters(users) {
try {
const letters = await this.model.getAllLetters();
this.view.renderLetters(letters, users); // Убедитесь, что users передается
} catch (error) {
console.error('Ошибка загрузки:', error);
this.view.showError('Не удалось загрузить письма');
}
}
bindEvents() {
if (e.target.id === 'addLetterBtn' || e.target.closest('#addLetterBtn')) {
this.view.showAddForm(this.users, this.folders);
}
document.addEventListener('click', (e) => {
if (e.target.id === 'addLetterBtn' || e.target.closest('#addLetterBtn')) {
this.view.showAddForm();
} else if (e.target.id === 'cancelAdd' || e.target.closest('#cancelAdd')) {
this.view.hideAddForm();
} else if (e.target.classList.contains('delete-btn') || e.target.closest('.delete-btn')) {
const id = e.target.dataset.id || e.target.closest('.delete-btn').dataset.id;
this.deleteLetter(id);
} else if (e.target.classList.contains('edit-btn') || e.target.closest('.edit-btn')) {
const id = e.target.dataset.id || e.target.closest('.edit-btn').dataset.id;
this.editLetter(id);
} else if (e.target.id === 'cancelEdit' || e.target.closest('#cancelEdit')) {
this.view.hideEditForm();
}
});
document.addEventListener('submit', async (e) => {
if (e.target.id === 'letterForm') {
e.preventDefault();
await this.handleFormSubmit();
} else if (e.target.id === 'editLetterFormElement') {
e.preventDefault();
await this.handleEditFormSubmit();
}
});
}
async editLetter(id) {
try {
console.log('Editing letter with ID:', id);
const letters = await this.model.getAllLetters();
console.log('All letters:', letters);
const letter = letters.find(l => l.id == id);
console.log('Found letter:', letter);
if (letter) {
this.view.showEditForm(letter);
} else {
this.view.showError('Письмо не найдено');
}
} catch (error) {
console.error('Ошибка при получении письма:', error);
this.view.showError(`Не удалось загрузить письмо для редактирования: ${error.message}`);
}
}
async handleFormSubmit() {
const subject = document.getElementById('subject').value.trim();
const username = document.getElementById('senderName').value.trim();
const message = document.getElementById('message').value.trim();
if (!username) {
this.view.showError('Введите имя пользователя для email');
return;
}
const senderName = username + '@mail.ru';
try {
const newLetter = await this.model.createLetter({
subject,
senderName,
message,
date: new Date().toLocaleDateString('ru-RU'),
folderId: 1
});
// Добавляем новое письмо в начало списка
const container = document.getElementById('lettersContainer');
const alert = container.querySelector('.alert');
if (alert) container.removeChild(alert);
const card = document.createElement('div');
card.className = 'card mb-3 letter-card';
card.innerHTML = `
<div class="card-body">
<h5 class="card-title">${subject}</h5>
<p class="card-text"><strong>От:</strong> ${senderName}</p>
<p class="card-text">${message}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">${new Date().toLocaleDateString('ru-RU')}</small>
<div>
<button class="btn btn-sm btn-primary edit-btn me-2" data-id="${newLetter.id}">
<i class="bi bi-pencil"></i> Редактировать
</button>
<button class="btn btn-sm btn-danger delete-btn" data-id="${newLetter.id}">
<i class="bi bi-trash"></i> Удалить
</button>
</div>
</div>
</div>
`;
container.insertBefore(card, container.firstChild);
this.view.hideAddForm();
} catch (error) {
console.error('Ошибка при создании письма:', error);
this.view.showError('Не удалось создать письмо');
}
}
async handleEditFormSubmit() {
const id = document.getElementById('editLetterForm').dataset.id;
const subject = document.getElementById('editSubject').value.trim();
const senderName = document.getElementById('editSenderName').value.trim();
const message = document.getElementById('editMessage').value.trim();
try {
await this.model.updateLetter(id, { subject, senderName, message });
window.location.reload(); // Просто перезагружаем страницу
} catch (error) {
console.log('Ошибка при редактировании:', error); // Только лог в консоль
}
}
async updateLetter(id, letterData) {
const response = await fetch(`${this.apiUrl}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...letterData,
date: new Date().toLocaleDateString('ru-RU'),
folderId: 1
}),
});
return await response.json();
}
async deleteLetter(id) {
if (confirm('Вы уверены, что хотите удалить это письмо?')) {
try {
await this.model.deleteLetter(id);
await this.loadLetters();
} catch (error) {
console.error('Ошибка удаления:', error);
this.view.showError('Не удалось удалить письмо');
}
}
}
}

53
db.json Normal file
View File

@@ -0,0 +1,53 @@
{
"_headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE"
},
"letters": [
{
"id": "0dbb",
"subject": "rta",
"senderName": "КАМЕНЩИК",
"message": "321",
"date": "14.05.2025",
"folderId": 1
},
{
"id": "cf4b",
"subject": "Gtgt",
"senderName": "CyberLoh",
"message": у тебя за спиной",
"date": "14.05.2025",
"folderId": 1
},
{
"id": "19a3",
"subject": "Gnoms",
"senderName": "BigBrain",
"message": "561",
"date": "14.05.2025",
"folderId": 1
},
{
"id": "5272",
"subject": "6565",
"senderName": "Try",
"message": "5ggtgt",
"date": "14.05.2025",
"folderId": 1
}
],
"users": [
{
"id": "1",
"name": "Иван Иванов",
"email": "ivan@example.com"
}
],
"folders": [
{
"id": "1",
"name": "Входящие"
}
]
}

82
model.js Normal file
View File

@@ -0,0 +1,82 @@
export class LetterModel {
constructor() {
this.apiUrl = 'http://localhost:3000';
this.currentUserId = 1;
}
async getUsers() {
const response = await fetch(`${this.apiUrl}/users`);
return await response.json();
}
// Получить все папки
async getFolders() {
const response = await fetch(`${this.apiUrl}/folders`);
return await response.json();
}
// Получить все письма (с данными о пользователях и папках)
async getAllLetters() {
try {
const response = await fetch(
`${this.apiUrl}/letters?_expand=user&_expand=folder`
);
if (!response.ok) throw new Error('Ошибка загрузки писем');
return await response.json();
} catch (error) {
console.error('Ошибка в getAllLetters:', error);
throw error;
}
}
// Получить письма конкретного пользователя
async getLettersByUserId(userId) {
const response = await fetch(
`${this.apiUrl}/letters?userId=${userId}&_expand=folder`
);
return await response.json();
}
// Получить письма из конкретной папки
async getLettersByFolderId(folderId) {
const response = await fetch(
`${this.apiUrl}/letters?folderId=${folderId}&_expand=user`
);
return await response.json();
}
// Создать письмо
async createLetter(letterData) {
const response = await fetch(`${this.apiUrl}/letters`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...letterData,
userId: this.currentUserId, // Автоматически добавляем userId
date: new Date().toLocaleDateString('ru-RU'), // Форматируем дату
folderId: letterData.folderId || 1 // Добавляем folderId по умолчанию
}),
});
return await response.json();
}
// Обновить письмо
async updateLetter(id, letterData) {
const response = await fetch(`${this.apiUrl}/letters/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(letterData),
});
return await response.json();
}
// Удалить письмо
async deleteLetter(id) {
await fetch(`${this.apiUrl}/letters/${id}`, {
method: 'DELETE',
});
}
}

5025
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "mysite",
"version": "1.0.0",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"dev": "concurrently \"json-server --watch db.json --port 3000\" \"live-server\"",
"test": "echo \"Error: no test specified\" && exit 1",
"start": "vite",
"build": "vite build",
"serve": "http-server -p 3000 ./dist/",
"prod": "npm-run-all build serve",
"lint": "eslint …"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"eslint": "8.56.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.0.2",
"eslint-plugin-html": "8.1.2",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.2.3",
"http-server": "14.1.1",
"npm-run-all": "4.1.5",
"vite": "6.2.0",
"concurrently": "^8.2.0",
"live-server": "^1.2.2"
},
"dependencies": {
"bootstrap": "^5.3.3",
"bootstrap-icons": "1.11.3",
"json-server": "^0.17.0"
}
}

219
view.js Normal file
View File

@@ -0,0 +1,219 @@
export class LetterView {
constructor() {
this.app = document.getElementById('app');
}
renderLetters(letters, users) {
// Сначала создаем контейнер для формы и списка писем
let html = `
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="mb-0">Список писем</h3>
<button id="addLetterBtn" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Добавить письмо
</button>
</div>
<!-- Форма добавления письма (изначально скрыта) -->
<div id="addLetterForm" class="card mb-3" style="display: none;">
<div class="card-body">
<h5 class="card-title">Новое письмо</h5>
<form id="letterForm">
<div class="mb-3">
<label for="subject" class="form-label">Тема</label>
<input type="text" class="form-control" id="subject" required>
</div>
<div class="mb-3">
<label for="senderName" class="form-label">От кого</label>
<input type="text" class="form-control" id="senderName" required>
</div>
<div class="mb-3">
<label for="message" class="form-label">Сообщение</label>
<textarea class="form-control" id="message" rows="5" required></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary flex-grow-1">Добавить</button>
<button type="button" id="cancelAdd" class="btn btn-outline-secondary">Отмена</button>
</div>
</form>
</div>
</div>
<!-- Контейнер для списка писем -->
<div id="lettersContainer" class="mb-4"></div>
`;
this.app.innerHTML = html;
const container = document.getElementById('lettersContainer');
if (letters.length === 0) {
container.innerHTML = `
<div class="alert alert-info">
Нет писем для отображения
</div>
`;
} else {
letters.forEach(letter => {
const user = users.find(u => u.id === letter.userId);
const card = document.createElement('div');
card.className = 'card mb-3 letter-card';
card.innerHTML = `
<div class="card-body">
<h5 class="card-title">${letter.subject}</h5>
<p class="card-text"><strong>От:</strong> ${letter.senderName}</p>
${user ? `<p class="card-text"><strong>Пользователь:</strong> ${user.name}</p>` : ''}
<p class="card-text">${letter.message}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">${letter.date}</small>
<div>
<button class="btn btn-sm btn-primary edit-btn me-2" data-id="${letter.id}">
<i class="bi bi-pencil"></i> Редактировать
</button>
<button class="btn btn-sm btn-danger delete-btn" data-id="${letter.id}">
<i class="bi bi-trash"></i> Удалить
</button>
</div>
</div>
</div>
`;
container.appendChild(card);
});
}
}
showAddForm(users = [], folders = []) {
const folderOptions = folders.map(f => `<option value="${f.id}">${f.name}</option>`).join('');
const formHtml = `
<div id="addLetterForm" class="card mb-3">
<div class="card-body">
<h5 class="card-title">Новое письмо</h5>
<form id="letterForm">
<div class="mb-3">
<label for="subject" class="form-label">Тема</label>
<input type="text" class="form-control" id="subject" required>
</div>
<div class="mb-3">
<label for="senderName" class="form-label">Отправитель (имя)</label>
<input type="text" class="form-control" id="senderName" required>
</div>
<div class="mb-3">
<label for="folderSelect" class="form-label">Папка</label>
<select id="folderSelect" class="form-select" required>
<option disabled selected>Выберите папку</option>
${folderOptions}
</select>
</div>
<div class="mb-3">
<label for="message" class="form-label">Сообщение</label>
<textarea id="message" class="form-control" rows="5" required></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary flex-grow-1">Добавить</button>
<a href="page2.html" class="btn btn-outline-secondary">Отмена</a>
</div>
</form>
</div>
</div>
`;
this.app.innerHTML = formHtml;
}
showEditForm(letter) {
const form = document.getElementById('editLetterForm');
if (!form) {
this.createEditForm(letter);
} else {
document.getElementById('editSubject').value = letter.subject;
document.getElementById('editSenderName').value = letter.senderName;
document.getElementById('editMessage').value = letter.message;
form.dataset.id = letter.id;
form.style.display = 'block';
}
document.getElementById('editSubject').focus();
}
hideEditForm() {
const form = document.getElementById('editLetterForm');
if (form) {
form.style.display = 'none';
form.reset();
}
}
createAddForm() {
const formHtml = `
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Новое письмо</h5>
<form id="letterForm">
<div class="mb-3">
<label for="subject" class="form-label">Тема</label>
<input type="text" class="form-control" id="subject" required>
</div>
<div class="mb-3">
<label for="senderName" class="form-label">От кого</label>
<input type="text" class="form-control" id="senderName" required>
</div>
<div class="mb-3">
<label for="message" class="form-label">Сообщение</label>
<textarea class="form-control" id="message" rows="5" required></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary flex-grow-1">Добавить</button>
<a href="page2.html" class="btn btn-outline-secondary">Отмена</a>
</div>
</form>
</div>
</div>
`;
this.app.innerHTML = formHtml;
}
createEditForm(letter) {
const formHtml = `
<div id="editLetterForm" class="card mb-3" style="display: none;" data-id="${letter.id}">
<div class="card-body">
<h5 class="card-title">Редактирование письма</h5>
<form id="editLetterFormElement">
<div class="mb-3">
<label for="editSubject" class="form-label">Тема</label>
<input type="text" class="form-control" id="editSubject" value="${letter.subject}" required>
</div>
<div class="mb-3">
<label for="editSenderName" class="form-label">От кого</label>
<input type="text" class="form-control" id="editSenderName" value="${letter.senderName}" required>
</div>
<div class="mb-3">
<label for="editMessage" class="form-label">Сообщение</label>
<textarea class="form-control" id="editMessage" rows="5" required>${letter.message}</textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary flex-grow-1">Сохранить</button>
<button type="button" id="cancelEdit" class="btn btn-outline-secondary">Отмена</button>
</div>
</form>
</div>
</div>
`;
this.app.insertAdjacentHTML('beforeend', formHtml);
}
hideAddForm() {
document.getElementById('addLetterForm').style.display = 'none';
document.getElementById('letterForm').reset();
}
showError(message) {
console.log(message);
}
showLoading() {
const loader = document.createElement('div');
loader.className = 'loader';
loader.innerHTML = 'Сохранение изменений...';
this.app.appendChild(loader);
}
hideLoading() {
const loader = document.querySelector('.loader');
if (loader) loader.remove();
}
}

BIN
vk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
Отчёт4.docx Normal file

Binary file not shown.