[#1] added base calendar and started to tweak ui

This commit is contained in:
Camille Moussu
2025-06-27 18:11:23 +02:00
parent cceb83519f
commit 624c1b43ca
19 changed files with 1593 additions and 238 deletions
+184
View File
@@ -0,0 +1,184 @@
main {
display: flex;
flex-direction: row;
}
.calendar {
width: 100%;
height: 100%;
}
.sidebar{
border-right: 2px gray;
}
.fc .fc-daygrid-day {
min-height: 10px !important; /* Default is ~40px */
height: 10px !important;
font-size: 0.75rem;
}
.fc .fc-timegrid-slot {
height: 60px !important; /* Default is ~40px */
}
#calendar {
flex: 1;
overflow: hidden; /* Important: let FullCalendar handle internal scroll */
}
.fc-scroller-liquid-absolute {
overflow-y: auto !important; /* This targets the scrollable part */
max-height: calc(100vh - 100px); /* Adjust based on your header size */
}
.scrollgrid-section-header {
border: white;
}
/* Base container */
.fc-daygrid-day-top {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem 0;
font-family: "Inter", sans-serif;
color: #1e293b; /* dark navy */
}
/* Day number */
.fc-daygrid-day-top .fc-daygrid-day-number {
font-size: 1rem;
font-weight: 600;
line-height: 2;
}
/* Day name (e.g., MON) */
.fc-daygrid-day-top small {
font-size: 0.75rem;
letter-spacing: 0.05em;
color: #94a3b8; /* slate gray */
text-transform: uppercase;
}
/* Todays date highlight */
.current-date {
background-color: orange;
color: white;
border-radius: 50%;
padding: 50px;
}
.fc .fc-col-header-cell,
.fc .fc-scrollgrid table,
.fc-theme-standard .fc-scrollgrid {
background: none;
border: none;
}
/* Container de la sidebar */
.sidebar-calendar {
width: 280px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
gap: 30px;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
/* Style du mini calendrier */
.react-calendar {
border: none;
font-size: 14px;
width: 100%;
user-select: none;
}
/* Jours sélectionnés et hover */
.react-calendar__tile {
transition: background-color 0.3s ease, color 0.3s ease;
border-radius: 50%;
border: none;
background-color: white;
width: 12px;
height: 37.52px;
}
.react-calendar__tile:hover {
background-color: rgb(224, 224, 224);
border: none;
border-radius: 50%;
cursor: pointer;
}
.react-calendar__tile--active {
background-color: orange;
color: white;
}
/* Titre ou header de la sidebar */
.sidebar-calendar h2 {
font-size: 1.3rem;
font-weight: 700;
color: #333;
margin: 0 0 10px 0;
}
/* Liste des calendriers */
.calendar-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 15px;
}
.calendar-list li {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
color: #444;
padding: 10px 15px;
border-radius: 10px;
transition: background-color 0.2s ease;
cursor: pointer;
}
/* Couleurs distinctes pour chaque type de calendrier */
.calendar-list li.work {
background-color: #d0e8ff;
color: #1a73e8;
}
.calendar-list li.work:hover {
background-color: #b0d4ff;
}
.calendar-list li.personal {
background-color: #ffe0e0;
color: #d93025;
}
.calendar-list li.personal:hover {
background-color: #ffb3b3;
}
/* Petit indicateur couleur à gauche */
.calendar-list li::before {
content: "";
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.calendar-list li.work::before {
background-color: #1a73e8;
}
.calendar-list li.personal::before {
background-color: #d93025;
}
+165
View File
@@ -0,0 +1,165 @@
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import ReactCalendar from "react-calendar";
import "./Calendar.css";
import { useRef, useState } from "react";
import { useAppSelector } from "../../app/hooks";
export default function CalendarApp() {
const calendarRef = useRef<CalendarApi | null>(null);
const [selectedDate, setSelectedDate] = useState(new Date());
const [selectedCalendars, setSelectedCalendars] = useState([
"Work",
"Personnal",
]);
const events = useAppSelector((state) => state.events);
const handleCalendarToggle = (name: string) => {
setSelectedCalendars((prev) =>
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
);
};
const filteredEvent = events.filter((e) =>
selectedCalendars.includes(e.calendar)
);
const [date, setDate] = useState(new Date());
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
return (
<main>
<div className="sidebar">
<div className="calendar-label">
<div className="calendar-label">
<span>
{months[date.getMonth()]} {date.getFullYear()}
</span>
</div>
<button
onClick={() =>
setDate(new Date(date.getFullYear(), date.getMonth() - 1))
}
>
&lt;
</button>
<button
onClick={() =>
setDate(new Date(date.getFullYear(), date.getMonth() + 1))
}
>
&gt;
</button>
</div>
<ReactCalendar
showNeighboringMonth={false}
calendarType="hebrew"
formatShortWeekday={(locale, date) =>
date.toLocaleDateString(locale, { weekday: "narrow" })
}
value={selectedDate}
onClickDay={(date) => {
setSelectedDate(date);
calendarRef.current?.gotoDate(date);
}}
prevLabel={null}
nextLabel={null}
showNavigation={false}
/>
{["Work", "Personnal", "Holidays"].map((name) => (
<div key={name}>
<label>
<input
type="checkbox"
checked={selectedCalendars.includes(name)}
onChange={() => handleCalendarToggle(name)}
/>
{name}
</label>
</div>
))}
</div>
<div className="calendar">
<FullCalendar
ref={(ref) => {
if (ref) {
calendarRef.current = ref.getApi();
}
}}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="timeGridWeek"
weekends={false}
editable={true}
selectable={true}
nowIndicator
selectMirror={true}
views={{
timeGridWeek: { titleFormat: { month: "long", year: "numeric" } },
}}
dayMaxEvents={true}
events={filteredEvent}
weekNumbers
weekNumberFormat={{ week: "long" }}
slotDuration={"00:30:00"}
slotLabelInterval={"00:30:00"}
scrollTime={"07:00:00"}
allDayText=""
slotLabelFormat={{
hour: "2-digit",
minute: "2-digit",
hour12: false,
}}
dayHeaderContent={(arg) => {
const date = arg.date.getDate();
const weekDay = arg.date
.toLocaleDateString("en-US", { weekday: "short" })
.toUpperCase();
return (
<div className="fc-daygrid-day-top">
<small>{weekDay}</small>
<span
className={`fc-daygrid-day-number ${
arg.isToday ? "current-date" : ""
}`}
>
{date}
</span>
</div>
);
}}
eventClick={(info) => {
info.jsEvent.preventDefault(); // don't let the browser navigate
if (info.event.url) {
window.open(info.event.url);
} else {
console.log(info.event);
}
}}
headerToolbar={{
left: "title",
center: "prev,today,next",
right: "dayGridMonth,timeGridWeek,timeGridDay",
}}
/>
</div>
</main>
);
}
+6
View File
@@ -0,0 +1,6 @@
import React from "react";
import logo from "../../static/images/calendar.svg";
export function Error() {
return <p>Error</p>;
}
+6
View File
@@ -0,0 +1,6 @@
import React from "react";
import logo from "../../static/images/calendar.svg";
export function Loading() {
return <img src={logo} />;
}
+1 -5
View File
@@ -16,7 +16,7 @@
padding: 0.2rem;
}
.calendar {
.calendar-text {
font-family: "Cal Sans";
padding: 0.2rem;
font-weight: 400;
@@ -39,12 +39,8 @@
display: flex;
justify-content: space-between;
align-items: center;
background-color: #1a1a1a62;
padding: 0.5rem;
color: white;
position: absolute;
width: 100%;
top: 0;
z-index: 1000;
}
+15 -42
View File
@@ -1,54 +1,27 @@
import React from "react";
import { Auth } from "../../features/User/oidcAuth";
import logo from "../../static/images/calendar.svg";
import "./Menubar.css";
import { useAppSelector } from "../../app/hooks";
export function Menubar() {
return (
<div className="menubar">
<div className="menubar-item tc-home">
<img className="logo" src={logo} />
<p>
<span className="twake">Twake</span>
<span className="calendar">Calendar</span>
</p>
</div>
<div className="menubar-item nav-month">
<p>Current Month</p>
<p className="day-selector"> droite today gauche</p>
</div>
<header className="menubar">
<MainTitle/>
<div className="menubar-item search-bar">
<p>big search bar</p>
</div>
<div className="menubar-item menu-tools">
<HandleLogin />
<div className="menubar-item">
<p>Account stuff</p>
</div>
</header>
);
}
export function MainTitle() {
return (
<div className="menubar-item tc-home">
<img className="logo" src={logo} />
<p>
<span className="twake">Twake</span>
<span className="calendar-text">Calendar</span>
</p>
</div>
);
}
function HandleLogin() {
const userData = useAppSelector((state) => state.user.userData);
if (!userData) {
return (
<button
onClick={async () => {
const loginurl = await Auth();
sessionStorage.setItem(
"redirectState",
JSON.stringify({
code_verifier: loginurl.code_verifier,
state: loginurl.state,
})
);
window.location.assign(loginurl.redirectTo);
}}
>
login
</button>
);
} else {
return <p>{userData.sub}</p>;
}
}