diff --git a/client/src/containers/User/IT-Ticket/CreateTicket.module.css b/client/src/containers/User/IT-Ticket/CreateTicket.module.css index 616d9d5..a699d07 100644 --- a/client/src/containers/User/IT-Ticket/CreateTicket.module.css +++ b/client/src/containers/User/IT-Ticket/CreateTicket.module.css @@ -1,182 +1,472 @@ -.page { - min-height: 100vh; - background: #050816; - display: flex; - justify-content: center; - align-items: center; - padding: 30px; - font-family: Inter, Arial, sans-serif; +.wrapper { position: relative; - overflow: hidden; - color: white; + z-index: 2; + + width: 100%; + max-width: 1500px; + + display: flex; + flex-direction: column; + gap: 24px; } -/* GRID BACKGROUND */ -.bgGrid { - position: fixed; - inset: 0; - background-image: - linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), - linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); - background-size: 60px 60px; - z-index: 0; +.hero { + text-align: center; + margin-bottom: 10px; } -/* GLOW EFFECTS */ -.glow1, -.glow2 { - position: fixed; - width: 500px; - height: 500px; - border-radius: 50%; - filter: blur(120px); - opacity: 0.25; - z-index: 0; +.grid { + display: grid; + grid-template-columns: 420px 1fr; + gap: 24px; } -.glow1 { - top: -120px; - left: -120px; - background: #2563eb; +.cardHeader h2 { + margin: 0; + font-size: 1.5rem; } -.glow2 { - bottom: -120px; - right: -120px; - background: #7c3aed; +.cardHeader p { + color: #94a3b8; + margin-top: 6px; + margin-bottom: 20px; } -/* CARD */ -.card { - position: relative; - z-index: 1; +.controls { + display: flex; + gap: 14px; + margin-bottom: 20px; +} - width: 100%; - max-width: 650px; +.searchInput { + flex: 1; + + background: rgba(255,255,255,0.05); + + border: 1px solid rgba(255,255,255,0.10); + + color: white; + + padding: 12px 14px; + + border-radius: 14px; +} + +.select { + background: rgba(255,255,255,0.05); + + border: 1px solid rgba(255,255,255,0.10); + + color: white; + + padding: 12px 14px; + + border-radius: 14px; +} + +.ticketList { + display: flex; + flex-direction: column; + gap: 16px; + + max-height: 700px; + overflow-y: auto; + padding-right: 6px; +} + +.ticketCard { background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.08); - backdrop-filter: blur(18px); - border-radius: 24px; - padding: 40px; + border-radius: 20px; + + padding: 18px; - box-shadow: 0 20px 80px rgba(0,0,0,0.5); + transition: 0.2s ease; } -/* TITLE */ -.title { - font-size: 2.4rem; - font-weight: 900; - text-align: center; +.ticketCard:hover { + transform: translateY(-3px); + + border-color: rgba(96,165,250,0.30); +} + +.ticketTop { + display: flex; + justify-content: space-between; + gap: 12px; - background: linear-gradient(90deg, #60a5fa, #818cf8, #c084fc); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; + align-items: center; +} + +.ticketTop h3 { + margin: 0; +} + +.ticketDescription { + color: #cbd5e1; + + line-height: 1.6; + + margin-top: 14px; +} + +.ticketFooter { + margin-top: 18px; + + display: flex; + justify-content: space-between; + + color: #94a3b8; + + font-size: 13px; +} + +.badge { + padding: 8px 12px; + + border-radius: 999px; + + font-size: 12px; + font-weight: 700; + + text-transform: uppercase; +} + +.open { + background: rgba(59,130,246,0.15); + color: #93c5fd; +} + +.progress { + background: rgba(234,179,8,0.15); + color: #fde68a; +} - margin-bottom: 8px; +.closed { + background: rgba(34,197,94,0.15); + color: #86efac; } -.subtitle { +.emptyState { + padding: 40px; text-align: center; - color: #9ca3af; - margin-bottom: 25px; + + color: #94a3b8; + + border: 1px dashed rgba(255,255,255,0.10); + + border-radius: 18px; } -/* FORM */ -.form { +/* ========================================================= + MODERN FORM +========================================================= */ + +.topGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; +} + +.inputWrap { display: flex; flex-direction: column; - gap: 14px; + gap: 10px; } -.input, -.textarea { - background: rgba(255,255,255,0.05); - border: 1px solid rgba(255,255,255,0.12); +.label { + font-size: 13px; + font-weight: 700; + color: #dbeafe; + + letter-spacing: 0.03em; +} + +.inputShell, +.textareaShell { + position: relative; + + background: + linear-gradient( + 180deg, + rgba(255,255,255,0.08), + rgba(255,255,255,0.04) + ); + + border: 1px solid rgba(255,255,255,0.08); + + border-radius: 20px; + + overflow: hidden; + + transition: 0.25s ease; + + backdrop-filter: blur(18px); +} + +.inputShell:hover, +.textareaShell:hover { + border-color: + rgba(96,165,250,0.35); +} + +.inputShell:focus-within, +.textareaShell:focus-within { + border-color: + rgba(96,165,250,0.55); + + box-shadow: + 0 0 0 4px rgba(96,165,250,0.12), + 0 20px 40px rgba(37,99,235,0.15); + + transform: translateY(-1px); +} + +.inputIcon { + position: absolute; + + top: 50%; + left: 18px; + + transform: translateY(-50%); + + font-size: 16px; + + opacity: 0.8; +} + +.inputModern { + width: 100%; + + background: transparent; + border: none; + color: white; - padding: 12px 14px; - border-radius: 12px; + padding: + 18px + 18px + 18px + 54px; + + font-size: 15px; + font-weight: 500; +} - font-size: 14px; +.inputModern::placeholder { + color: #64748b; } -.input:focus, -.textarea:focus { +.inputModern:focus { outline: none; - border-color: #60a5fa; - box-shadow: 0 0 0 4px rgba(96,165,250,0.15); } -/* CHECKBOX */ -.checkboxRow { - display: flex; - align-items: center; - gap: 10px; - color: #cbd5e1; - font-size: 14px; +.textareaModern { + width: 100%; + min-height: 180px; + + background: transparent; + border: none; + + resize: vertical; + + color: white; + + padding: 20px; + + font-size: 15px; + line-height: 1.7; +} + +.textareaModern::placeholder { + color: #64748b; +} + +.textareaModern:focus { + outline: none; +} + +/* ========================================================= + PRIORITY GRID +========================================================= */ + +.priorityGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 14px; } -/* BUTTON */ -.primaryBtn { - margin-top: 10px; +.priorityCard { + position: relative; + + overflow: hidden; + + background: + linear-gradient( + 180deg, + rgba(255,255,255,0.05), + rgba(255,255,255,0.02) + ); + + border: 1px solid rgba(255,255,255,0.08); + + border-radius: 22px; + + padding: 20px; - background: linear-gradient(90deg, #2563eb, #7c3aed); color: white; - padding: 14px; + cursor: pointer; + + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + + transition: 0.25s ease; +} + +.priorityCard:hover { + transform: translateY(-4px); + + border-color: + rgba(96,165,250,0.35); + + box-shadow: + 0 15px 40px rgba(0,0,0,0.20); +} + +.priorityCard span { + font-size: 22px; +} + +.priorityCard strong { + font-size: 15px; +} + +.priorityCard small { + color: #94a3b8; + line-height: 1.5; +} + +.priorityActive { + background: + linear-gradient( + 135deg, + rgba(37,99,235,0.25), + rgba(124,58,237,0.20) + ); + + border-color: + rgba(96,165,250,0.50); + + box-shadow: + 0 20px 50px rgba(37,99,235,0.25); +} + +/* ========================================================= + SUBMIT BUTTON +========================================================= */ + +.submitBtn { + position: relative; + + overflow: hidden; + + margin-top: 8px; + border: none; - border-radius: 14px; - font-weight: 700; + border-radius: 24px; + + padding: 20px; + cursor: pointer; - transition: 0.2s ease; - box-shadow: 0 10px 30px rgba(59,130,246,0.25); + background: + linear-gradient( + 90deg, + #2563eb, + #7c3aed, + #2563eb + ); + + background-size: 300%; + + color: white; + + font-size: 15px; + font-weight: 800; + + transition: 0.3s ease; + + box-shadow: + 0 25px 60px rgba(37,99,235,0.30); + + animation: gradientMove 8s linear infinite; } -.primaryBtn:hover { - transform: translateY(-2px); +.submitBtn:hover { + transform: + translateY(-3px) + scale(1.01); + + box-shadow: + 0 35px 80px rgba(37,99,235,0.45); } -.primaryBtn:disabled { +.submitBtn:disabled { opacity: 0.6; cursor: not-allowed; } -/* SUCCESS + ERROR */ -.successBox { - margin-top: 20px; - padding: 14px; - border-radius: 12px; +.submitBtn::before { + content: ""; - background: rgba(34,197,94,0.1); - border: 1px solid rgba(34,197,94,0.3); - color: #86efac; -} + position: absolute; + + top: 0; + left: -120%; + + width: 100%; + height: 100%; -.errorBox { - margin-top: 20px; - padding: 14px; - border-radius: 12px; + background: + linear-gradient( + 90deg, + transparent, + rgba(255,255,255,0.25), + transparent + ); - background: rgba(239,68,68,0.1); - border: 1px solid rgba(239,68,68,0.3); - color: #fca5a5; + transition: 0.7s; } -/* BACK LINK */ -.backLink { - display: block; - text-align: center; - margin-top: 20px; +.submitBtn:hover::before { + left: 120%; +} + +/* ========================================================= + RESPONSIVE +========================================================= */ + +@media (max-width: 900px) { + + .topGrid { + grid-template-columns: 1fr; + } + + .priorityGrid { + grid-template-columns: 1fr; + } - color: #93c5fd; - text-decoration: none; - font-weight: 600; } -.backLink:hover { - text-decoration: underline; -} \ No newline at end of file +@media (max-width: 1200px) { + .grid { + grid-template-columns: 1fr; + } +} diff --git a/client/src/containers/User/IT-Ticket/CreateTicket.tsx b/client/src/containers/User/IT-Ticket/CreateTicket.tsx index 6911855..2e3edf2 100644 --- a/client/src/containers/User/IT-Ticket/CreateTicket.tsx +++ b/client/src/containers/User/IT-Ticket/CreateTicket.tsx @@ -1,238 +1,670 @@ -// import React, { useState, useEffect } from "react"; -// import { Link } from "react-router-dom"; -// import Button from "react-bootstrap/Button"; -// import Form from "react-bootstrap/Form"; -// import Alert from "react-bootstrap/Alert"; - -// const CreateTicket = () => { -// const [name, setName] = useState(""); -// const [subject, setSubject] = useState(""); -// const [description, setDescription] = useState(""); -// const [response, setResponse] = useState(null); -// const [error, setError] = useState(null); - -// const handleSubmit = (e: React.FormEvent) => { -// e.preventDefault(); - -// // Basic validation -// if (!name || !subject || !description) { -// setError("All fields are required."); -// return; -// } - -// const userData = { name, subject, description }; - -// // Send data to the API -// fetch("/api/it-help/post-ticket", { -// method: "POST", -// headers: { -// "Content-Type": "application/json", -// }, -// body: JSON.stringify(userData), -// }) -// .then((res) => { -// if (!res.ok) { -// throw new Error("Network response was not ok"); -// } -// return res.json(); -// }) -// .then((data) => { -// setResponse(data); -// setError(null); // Clear any previous error -// }) -// .catch((error) => { -// console.error("Error:", error); -// setError("There was an error submitting your ticket."); -// }); -// }; - -// useEffect(() => { -// if (response) { -// console.log("Response from API:", response); -// } -// }, [response]); - -// return ( -//
-//

Create IT Ticket

-//
-// -// Name -// setName(e.target.value)} -// placeholder="Enter Your Name" -// required -// /> -// - -// -// Subject -// setSubject(e.target.value)} -// placeholder="Subject" -// required -// /> -// - -// -// Description -// setDescription(e.target.value)} -// placeholder="Description" -// required -// /> -// - -// -// -// - -// -//
- -// {response && ( -// -//

Response from API:

-//
{JSON.stringify(response, null, 2)}
-//
-// )} - -// {error && ( -// -// {error} -// -// )} - -// -// Home -// -//
-// ); -// }; - -// export default CreateTicket; -import React, { useState, useEffect } from "react"; +import React, { + useEffect, + useMemo, + useState, +} from "react"; + +import axios from "axios"; + import { Link } from "react-router-dom"; + import styles from "./CreateTicket.module.css"; +interface Ticket { + _id: string; + + name?: string; + + email?: string; + + subject?: string; + + description?: string; + + status?: string; + + priority?: string; + + createdAt?: string; +} + const CreateTicket = () => { - const [name, setName] = useState(""); - const [subject, setSubject] = useState(""); - const [description, setDescription] = useState(""); - const [response, setResponse] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!name || !subject || !description) { - setError("All fields are required."); - setResponse(null); - return; + const [name, setName] = + useState(""); + + const [email, setEmail] = + useState(""); + + const [subject, setSubject] = + useState(""); + + const [ + description, + setDescription, + ] = useState(""); + + const [priority, setPriority] = + useState("normal"); + + const [loading, setLoading] = + useState(false); + + const [success, setSuccess] = + useState(""); + + const [error, setError] = + useState(""); + + const [tickets, setTickets] = + useState([]); + + const [search, setSearch] = + useState(""); + + const [ + sortBy, + setSortBy, + ] = useState("newest"); + + // ----------------------------------- + // FETCH USER TICKETS + // ----------------------------------- + + const fetchTickets = async ( + userEmail: string + ) => { + try { + const res = await axios.get( + "/api/it-help/view" + ); + + const filtered = + res.data.filter( + (ticket: Ticket) => + ticket.email === + userEmail + ); + + setTickets(filtered); + } catch (err) { + console.error(err); } + }; + + // ----------------------------------- + // SUBMIT + // ----------------------------------- + + const submitTicket = + async ( + e: React.FormEvent + ) => { + e.preventDefault(); + + setLoading(true); + setSuccess(""); + setError(""); + + try { + await axios.post( + "/api/it-help/post-ticket", + { + name, + email, + subject, + description, + priority, + status: "open", + } + ); + + setSuccess( + "Ticket created successfully." + ); - setLoading(true); - - const userData = { name, subject, description }; - - fetch("/api/it-help/post-ticket", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(userData), - }) - .then((res) => { - if (!res.ok) throw new Error("Network error"); - return res.json(); - }) - .then((data) => { - setResponse(data); - setError(null); - setName(""); setSubject(""); setDescription(""); - }) - .catch(() => setError("There was an error submitting your ticket.")) - .finally(() => setLoading(false)); - }; + setPriority("normal"); + + fetchTickets(email); + } catch (err) { + console.error(err); + + setError( + "Unable to create ticket." + ); + } finally { + setLoading(false); + } + }; + + // ----------------------------------- + // LOAD TICKETS AFTER EMAIL + // ----------------------------------- useEffect(() => { - if (response) console.log("Ticket response:", response); - }, [response]); + if (email.trim()) { + fetchTickets(email); + } + }, [email]); + + // ----------------------------------- + // FILTER + SORT + // ----------------------------------- + + const filteredTickets = + useMemo(() => { + let filtered = tickets.filter( + (ticket) => + ticket.subject + ?.toLowerCase() + .includes( + search.toLowerCase() + ) || + ticket.description + ?.toLowerCase() + .includes( + search.toLowerCase() + ) + ); + + if (sortBy === "newest") { + filtered.sort( + (a, b) => + new Date( + b.createdAt || "" + ).getTime() - + new Date( + a.createdAt || "" + ).getTime() + ); + } + + if (sortBy === "oldest") { + filtered.sort( + (a, b) => + new Date( + a.createdAt || "" + ).getTime() - + new Date( + b.createdAt || "" + ).getTime() + ); + } + + return filtered; + }, [tickets, search, sortBy]); + + // ----------------------------------- + // RENDER + // ----------------------------------- return (
-
-
-
- -
-

Create IT Ticket

-

- Submit a request and our IT team will respond shortly -

- -
- setName(e.target.value)} - /> - - setSubject(e.target.value)} - /> - -