Readjusting styles and adding more implementations and fixing bugs
This commit is contained in:
parent
c817b09c9f
commit
c5a98435b3
|
|
@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
@ -70,7 +71,8 @@ public class NotificationController {
|
||||||
|
|
||||||
Set<User> existingUsers = updated.getUsers();
|
Set<User> existingUsers = updated.getUsers();
|
||||||
if (existingUsers != null) {
|
if (existingUsers != null) {
|
||||||
for (User user : existingUsers) {
|
List<User> clonedUsers = new ArrayList<>(existingUsers);
|
||||||
|
for (User user : clonedUsers) {
|
||||||
notificationService.removeNotificationFromUser(updated, user);
|
notificationService.removeNotificationFromUser(updated, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +87,7 @@ public class NotificationController {
|
||||||
return ResponseEntity.ok(toDTO(updated));
|
return ResponseEntity.ok(toDTO(updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "Delete a notification by ID", description = "Delete a notification by its ID")
|
@Operation(summary = "Delete a notification by ID", description = "Delete a notification by its ID")
|
||||||
@DeleteMapping("/delete/{id}")
|
@DeleteMapping("/delete/{id}")
|
||||||
public ResponseEntity<String> delete(@PathVariable Integer id) {
|
public ResponseEntity<String> delete(@PathVariable Integer id) {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -18,4 +18,5 @@ api.interceptors.request.use(
|
||||||
(error) => Promise.reject(error)
|
(error) => Promise.reject(error)
|
||||||
);
|
);
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import api from "../../api/axiosConfig";
|
import api from "../../api/axiosConfig";
|
||||||
|
|
||||||
|
|
||||||
const IVATypeManager = () => {
|
const IVATypeManager = () => {
|
||||||
const [ivaTypes, setIvaTypes] = useState([]);
|
const [ivaTypes, setIvaTypes] = useState([]);
|
||||||
const [newIva, setNewIva] = useState({ percentage: "", description: "" });
|
const [newIva, setNewIva] = useState({ percentage: "", description: "" });
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import api from '../../api/axiosConfig';
|
import api from '../../api/axiosConfig';
|
||||||
import '../styles/FormStyles.css';
|
import '../styles/ContentArea.css';
|
||||||
|
|
||||||
const NotificationCreateForm = () => {
|
const NotificationCreateForm = () => {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
@ -82,7 +82,7 @@ const NotificationCreateForm = () => {
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Crear nueva notificación</h2>
|
<h2>Crear nueva notificación</h2>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="notification-form">
|
<form onSubmit={handleSubmit} className="form-column">
|
||||||
<input type="text" name="title" placeholder="Título" value={notificationData.title} onChange={handleChange} required />
|
<input type="text" name="title" placeholder="Título" value={notificationData.title} onChange={handleChange} required />
|
||||||
<textarea name="message" placeholder="Mensaje" value={notificationData.message} onChange={handleChange} rows="4" required />
|
<textarea name="message" placeholder="Mensaje" value={notificationData.message} onChange={handleChange} rows="4" required />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,10 +171,8 @@ const UserCreateForm = () => {
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Crear nuevo usuario</h2>
|
<h2>Crear nuevo usuario</h2>
|
||||||
<form
|
<form onSubmit={handleSubmit} className="form-column">
|
||||||
onSubmit={handleSubmit}
|
|
||||||
style={{ display: "flex", flexDirection: "column", gap: "15px" }}
|
|
||||||
>
|
|
||||||
{/* Datos básicos */}
|
{/* Datos básicos */}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -264,6 +262,7 @@ const UserCreateForm = () => {
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
name="birthdate"
|
name="birthdate"
|
||||||
|
placeholder="Fecha de nacimiento"
|
||||||
value={studentData.birthdate}
|
value={studentData.birthdate}
|
||||||
onChange={handleChangeStudent}
|
onChange={handleChangeStudent}
|
||||||
required
|
required
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
// src/components/lists/NotificationList.jsx
|
import React, { useEffect, useState } from "react";
|
||||||
import React, { useEffect, useState } from 'react';
|
import api from "../../api/axiosConfig";
|
||||||
import api from '../../api/axiosConfig';
|
import ErrorMessage from "../common/ErrorMessage";
|
||||||
import '../styles/ContentArea.css'; // Tu estilo general de tarjetas y tablas
|
import "../styles/ContentArea.css";
|
||||||
|
|
||||||
const NotificationList = () => {
|
const NotificationList = () => {
|
||||||
const [notifications, setNotifications] = useState([]);
|
const [notifications, setNotifications] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [editingId, setEditingId] = useState(null);
|
||||||
|
const [editedData, setEditedData] = useState({});
|
||||||
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
|
const [successMsg, setSuccessMsg] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchNotifications();
|
fetchNotifications();
|
||||||
|
|
@ -13,21 +17,77 @@ const NotificationList = () => {
|
||||||
|
|
||||||
const fetchNotifications = async () => {
|
const fetchNotifications = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/notifications/getAll');
|
const res = await api.get("/notifications/getAll");
|
||||||
setNotifications(res.data);
|
setNotifications(res.data);
|
||||||
setLoading(false);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error al cargar notificaciones', err);
|
console.error("Error al cargar notificaciones", err);
|
||||||
|
setErrorMsg("❌ Error al cargar notificaciones.");
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) return <div className="card"><p>Cargando notificaciones...</p></div>;
|
const handleEditClick = (n) => {
|
||||||
|
setEditingId(n.id);
|
||||||
|
setEditedData({
|
||||||
|
title: n.title,
|
||||||
|
message: n.message,
|
||||||
|
shippingDate: n.shippingDate.split("T")[0],
|
||||||
|
type: n.type || "",
|
||||||
|
status: n.status,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setEditedData((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
try {
|
||||||
|
await api.put(`/notifications/update/${editingId}`, {
|
||||||
|
...editedData,
|
||||||
|
shippingDate: new Date(editedData.shippingDate).toISOString(),
|
||||||
|
});
|
||||||
|
setSuccessMsg("✅ Notificación actualizada correctamente");
|
||||||
|
setErrorMsg("");
|
||||||
|
setEditingId(null);
|
||||||
|
fetchNotifications();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setErrorMsg("❌ Error al actualizar la notificación");
|
||||||
|
setSuccessMsg("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
if (!window.confirm("¿Eliminar esta notificación?")) return;
|
||||||
|
try {
|
||||||
|
await api.delete(`/notifications/delete/${id}`);
|
||||||
|
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
||||||
|
setSuccessMsg("✅ Notificación eliminada");
|
||||||
|
setErrorMsg("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setErrorMsg("❌ Error al eliminar la notificación");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading)
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<p>Cargando notificaciones...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Listado de Notificaciones</h2>
|
<h2>Listado de Notificaciones</h2>
|
||||||
|
|
||||||
|
{/* ✅ Mensajes reutilizables */}
|
||||||
|
<ErrorMessage message={errorMsg} type="error" />
|
||||||
|
<ErrorMessage message={successMsg} type="success" />
|
||||||
|
|
||||||
{notifications.length === 0 ? (
|
{notifications.length === 0 ? (
|
||||||
<p>No hay notificaciones registradas.</p>
|
<p>No hay notificaciones registradas.</p>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -40,16 +100,89 @@ const NotificationList = () => {
|
||||||
<th>Fecha de Envío</th>
|
<th>Fecha de Envío</th>
|
||||||
<th>Tipo</th>
|
<th>Tipo</th>
|
||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
|
<th>Acciones</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{notifications.map((n) => (
|
{notifications.map((n) => (
|
||||||
<tr key={n.id}>
|
<tr key={n.id}>
|
||||||
<td>{n.title}</td>
|
{editingId === n.id ? (
|
||||||
<td>{n.message}</td>
|
<>
|
||||||
<td>{n.shippingDate.split('T')[0]}</td>
|
<td>
|
||||||
<td>{n.type}</td>
|
<input
|
||||||
<td>{n.status}</td>
|
name="title"
|
||||||
|
value={editedData.title}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
rows="2"
|
||||||
|
value={editedData.message}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name="shippingDate"
|
||||||
|
value={editedData.shippingDate}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
name="type"
|
||||||
|
value={editedData.type}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
name="status"
|
||||||
|
value={editedData.status}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<option value="ACTIVE">Activo</option>
|
||||||
|
<option value="INACTIVE">Inactivo</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button className="edit-btn" onClick={handleUpdate}>
|
||||||
|
✅
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="delete-btn"
|
||||||
|
onClick={() => setEditingId(null)}
|
||||||
|
>
|
||||||
|
❌
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<td>{n.title}</td>
|
||||||
|
<td>{n.message}</td>
|
||||||
|
<td>{n.shippingDate.split("T")[0]}</td>
|
||||||
|
<td>{n.type}</td>
|
||||||
|
<td>{n.status}</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
className="edit-btn"
|
||||||
|
onClick={() => handleEditClick(n)}
|
||||||
|
>
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="delete-btn"
|
||||||
|
onClick={() => handleDelete(n.id)}
|
||||||
|
>
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import "../styles/ContentArea.css";
|
||||||
const UserList = () => {
|
const UserList = () => {
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [editingUserId, setEditingUserId] = useState(null);
|
||||||
|
const [editedUserData, setEditedUserData] = useState({});
|
||||||
const [errorMsg, setErrorMsg] = useState("");
|
const [errorMsg, setErrorMsg] = useState("");
|
||||||
const [successMsg, setSuccessMsg] = useState("");
|
const [successMsg, setSuccessMsg] = useState("");
|
||||||
|
|
||||||
|
|
@ -13,20 +15,6 @@ const UserList = () => {
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formatRoleName = (roleName) => {
|
|
||||||
switch (roleName) {
|
|
||||||
case "ROLE_STUDENT":
|
|
||||||
return "Estudiante";
|
|
||||||
case "ROLE_TEACHER":
|
|
||||||
return "Profesor";
|
|
||||||
case "ROLE_ADMIN":
|
|
||||||
return "Administrador";
|
|
||||||
default:
|
|
||||||
return roleName;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/users/getAll");
|
const res = await api.get("/users/getAll");
|
||||||
|
|
@ -41,9 +29,45 @@ const UserList = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatRoleName = (roleName) => {
|
||||||
|
switch (roleName) {
|
||||||
|
case "ROLE_STUDENT": return "Estudiante";
|
||||||
|
case "ROLE_TEACHER": return "Profesor";
|
||||||
|
case "ROLE_ADMIN": return "Administrador";
|
||||||
|
default: return roleName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditClick = (user) => {
|
||||||
|
setEditingUserId(user.id);
|
||||||
|
setEditedUserData({ ...user });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditChange = (e) => {
|
||||||
|
setEditedUserData({
|
||||||
|
...editedUserData,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmUpdate = async () => {
|
||||||
|
try {
|
||||||
|
const res = await api.put(`/users/update/${editingUserId}`, editedUserData);
|
||||||
|
const updatedUser = res.data;
|
||||||
|
setUsers(users.map((u) => (u.id === updatedUser.id ? updatedUser : u)));
|
||||||
|
setEditingUserId(null);
|
||||||
|
setEditedUserData({});
|
||||||
|
setSuccessMsg("✅ Usuario actualizado correctamente.");
|
||||||
|
setErrorMsg("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const msg = err.response?.data?.message || "❌ Error al actualizar el usuario.";
|
||||||
|
setErrorMsg(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
if (!window.confirm("¿Estás seguro de que deseas eliminar este usuario?")) return;
|
if (!window.confirm("¿Estás seguro de que deseas eliminar este usuario?")) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.delete(`/users/delete/${id}`);
|
await api.delete(`/users/delete/${id}`);
|
||||||
setUsers(users.filter((u) => u.id !== id));
|
setUsers(users.filter((u) => u.id !== id));
|
||||||
|
|
@ -77,6 +101,8 @@ const UserList = () => {
|
||||||
<th>Nombre</th>
|
<th>Nombre</th>
|
||||||
<th>Apellido</th>
|
<th>Apellido</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
|
<th>Teléfono</th>
|
||||||
|
<th>Dirección</th>
|
||||||
<th>Rol</th>
|
<th>Rol</th>
|
||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th>Acciones</th>
|
<th>Acciones</th>
|
||||||
|
|
@ -85,15 +111,40 @@ const UserList = () => {
|
||||||
<tbody>
|
<tbody>
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<tr key={user.id}>
|
<tr key={user.id}>
|
||||||
<td>{user.name}</td>
|
{editingUserId === user.id ? (
|
||||||
<td>{user.surname}</td>
|
<>
|
||||||
<td>{user.email}</td>
|
<td><input name="name" value={editedUserData.name} onChange={handleEditChange} /></td>
|
||||||
<td>{formatRoleName(user.roleName)}</td>
|
<td><input name="surname" value={editedUserData.surname} onChange={handleEditChange} /></td>
|
||||||
<td>{user.status}</td>
|
<td><input name="email" value={editedUserData.email} onChange={handleEditChange} /></td>
|
||||||
<td>
|
<td><input name="phoneNumber" value={editedUserData.phoneNumber} onChange={handleEditChange} /></td>
|
||||||
<button className="edit-btn" onClick={() => alert("✏️ Edición no implementada")}>✏️</button>
|
<td><input name="address" value={editedUserData.address} onChange={handleEditChange} /></td>
|
||||||
<button className="delete-btn" onClick={() => handleDelete(user.id)}>🗑️</button>
|
<td>{formatRoleName(user.roleName)}</td>
|
||||||
</td>
|
<td>
|
||||||
|
<select name="status" value={editedUserData.status} onChange={handleEditChange}>
|
||||||
|
<option value="ACTIVE">Activo</option>
|
||||||
|
<option value="INACTIVE">Inactivo</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button className="edit-btn" onClick={handleConfirmUpdate}>✅</button>
|
||||||
|
<button className="delete-btn" onClick={() => setEditingUserId(null)}>❌</button>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<td>{user.name}</td>
|
||||||
|
<td>{user.surname}</td>
|
||||||
|
<td>{user.email}</td>
|
||||||
|
<td>{user.phoneNumber}</td>
|
||||||
|
<td>{user.address}</td>
|
||||||
|
<td>{formatRoleName(user.roleName)}</td>
|
||||||
|
<td>{user.status}</td>
|
||||||
|
<td>
|
||||||
|
<button className="edit-btn" onClick={() => handleEditClick(user)}>✏️</button>
|
||||||
|
<button className="delete-btn" onClick={() => handleDelete(user.id)}>🗑️</button>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,10 @@ form {
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form input, form select {
|
form input,
|
||||||
|
form select {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid #dcdde1;
|
border: 1px solid #dcdde1;
|
||||||
|
|
@ -87,6 +90,7 @@ form input, form select {
|
||||||
transition: border-color 0.3s;
|
transition: border-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
form input:focus, form select:focus {
|
form input:focus, form select:focus {
|
||||||
border-color: #0984e3;
|
border-color: #0984e3;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
|
@ -212,4 +216,54 @@ ul li button:hover {
|
||||||
background-color: #27ae60;
|
background-color: #27ae60;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-checkbox input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: #0984e3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-checkbox:hover {
|
||||||
|
background-color: #f0f4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #dcdde1;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
form textarea:focus {
|
||||||
|
border-color: #0984e3;
|
||||||
|
background-color: #ffffff;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue