New renders+css
This commit is contained in:
parent
73e284660f
commit
b3f47e93e8
22 changed files with 763 additions and 3 deletions
238
wwwroot/css/calendar-v2.css
Normal file
238
wwwroot/css/calendar-v2.css
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
:root {
|
||||
--hour-height: 60px;
|
||||
--header-height: 60px;
|
||||
--time-axis-width: 60px;
|
||||
--grid-columns: 5;
|
||||
--color-border: #e0e0e0;
|
||||
--color-surface: #fff;
|
||||
--color-text: #333;
|
||||
--color-text-secondary: #666;
|
||||
--color-primary: #1976d2;
|
||||
--color-hour-line: #e0e0e0;
|
||||
--color-current-time: #f44336;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.calendar-wrapper {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
swp-calendar {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
height: 100%;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
/* Nav */
|
||||
swp-calendar-nav {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
swp-nav-button {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
swp-nav-button:hover { background: #f0f0f0; }
|
||||
|
||||
swp-week-info {
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
swp-week-number {
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
}
|
||||
|
||||
swp-date-range {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
swp-calendar-container {
|
||||
display: grid;
|
||||
grid-template-columns: var(--time-axis-width) 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-header-spacer {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
background: var(--color-surface);
|
||||
border-right: 1px solid var(--color-border);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Time axis */
|
||||
swp-time-axis {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
border-right: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-time-axis-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
swp-hour-marker {
|
||||
height: var(--hour-height);
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Grid container */
|
||||
swp-grid-container {
|
||||
grid-column: 2;
|
||||
grid-row: 1 / 3;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
swp-calendar-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--grid-columns), 1fr);
|
||||
grid-auto-rows: auto;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
/* Single level: date only */
|
||||
swp-calendar-header[data-levels="date"] > swp-day-header { grid-row: 1; }
|
||||
|
||||
/* Two levels: resource + date */
|
||||
swp-calendar-header[data-levels="resource date"] > swp-resource-header { grid-row: 1; }
|
||||
swp-calendar-header[data-levels="resource date"] > swp-day-header { grid-row: 2; }
|
||||
|
||||
/* Three levels: team + resource + date */
|
||||
swp-calendar-header[data-levels="team resource date"] > swp-team-header { grid-row: 1; }
|
||||
swp-calendar-header[data-levels="team resource date"] > swp-resource-header { grid-row: 2; }
|
||||
swp-calendar-header[data-levels="team resource date"] > swp-day-header { grid-row: 3; }
|
||||
|
||||
swp-day-header {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
swp-day-header:last-child { border-right: none; }
|
||||
|
||||
swp-day-name {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
swp-day-date {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
swp-team-header {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
font-weight: 500;
|
||||
border-right: 1px solid var(--color-border);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
swp-resource-header {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
background: #fafafa;
|
||||
font-size: 13px;
|
||||
border-right: 1px solid var(--color-border);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Scrollable content */
|
||||
swp-scrollable-content {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
swp-time-grid {
|
||||
position: relative;
|
||||
min-height: calc(15 * var(--hour-height));
|
||||
}
|
||||
|
||||
swp-grid-lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
transparent calc(var(--hour-height) - 1px),
|
||||
var(--color-hour-line) calc(var(--hour-height) - 1px),
|
||||
var(--color-hour-line) var(--hour-height)
|
||||
);
|
||||
}
|
||||
|
||||
swp-day-columns {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--grid-columns), 1fr);
|
||||
}
|
||||
|
||||
swp-day-column {
|
||||
position: relative;
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
swp-day-column:last-child { border-right: none; }
|
||||
|
||||
swp-events-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
/* Events */
|
||||
swp-event {
|
||||
position: absolute;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
swp-event-time {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
swp-event-title {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
}
|
||||
131
wwwroot/v2.html
Normal file
131
wwwroot/v2.html
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="da">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Calendar V2</title>
|
||||
<link rel="stylesheet" href="css/calendar-v2.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="calendar-wrapper">
|
||||
<swp-calendar>
|
||||
<swp-calendar-nav>
|
||||
<swp-nav-button id="btn-simple">Datoer</swp-nav-button>
|
||||
<swp-nav-button id="btn-resource">Resources</swp-nav-button>
|
||||
<swp-nav-button id="btn-team">Teams</swp-nav-button>
|
||||
<swp-week-info>
|
||||
<swp-week-number>V2</swp-week-number>
|
||||
<swp-date-range id="view-info"></swp-date-range>
|
||||
</swp-week-info>
|
||||
</swp-calendar-nav>
|
||||
|
||||
<swp-calendar-container>
|
||||
<swp-header-spacer></swp-header-spacer>
|
||||
<swp-time-axis>
|
||||
<swp-time-axis-content id="time-axis"></swp-time-axis-content>
|
||||
</swp-time-axis>
|
||||
<swp-grid-container>
|
||||
<swp-calendar-header></swp-calendar-header>
|
||||
<swp-scrollable-content>
|
||||
<swp-time-grid>
|
||||
<swp-grid-lines></swp-grid-lines>
|
||||
<swp-day-columns></swp-day-columns>
|
||||
</swp-time-grid>
|
||||
</swp-scrollable-content>
|
||||
</swp-grid-container>
|
||||
</swp-calendar-container>
|
||||
</swp-calendar>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import {
|
||||
CalendarOrchestrator,
|
||||
RendererRegistry,
|
||||
StoreRegistry,
|
||||
DateRenderer,
|
||||
ResourceRenderer,
|
||||
TeamRenderer
|
||||
} from './js/calendar-v2.js';
|
||||
|
||||
const rendererRegistry = new RendererRegistry([
|
||||
new DateRenderer(),
|
||||
new ResourceRenderer(),
|
||||
new TeamRenderer()
|
||||
]);
|
||||
|
||||
const mockTeams = [
|
||||
{ id: 'alpha', name: 'Team Alpha' },
|
||||
{ id: 'beta', name: 'Team Beta' }
|
||||
];
|
||||
|
||||
const mockResources = [
|
||||
{ id: 'alice', name: 'Alice', teamId: 'alpha' },
|
||||
{ id: 'bob', name: 'Bob', teamId: 'alpha' },
|
||||
{ id: 'carol', name: 'Carol', teamId: 'beta' },
|
||||
{ id: 'dave', name: 'Dave', teamId: 'beta' }
|
||||
];
|
||||
|
||||
const storeRegistry = new StoreRegistry();
|
||||
storeRegistry.register('team', { getByIds: ids => mockTeams.filter(t => ids.includes(t.id)) });
|
||||
storeRegistry.register('resource', { getByIds: ids => mockResources.filter(r => ids.includes(r.id)) });
|
||||
|
||||
const orchestrator = new CalendarOrchestrator(rendererRegistry, storeRegistry);
|
||||
const container = document.querySelector('swp-calendar-container');
|
||||
const viewInfo = document.getElementById('view-info');
|
||||
|
||||
function getWeekDates() {
|
||||
const today = new Date();
|
||||
const mon = new Date(today);
|
||||
mon.setDate(today.getDate() - today.getDay() + 1);
|
||||
return Array.from({ length: 5 }, (_, i) => {
|
||||
const d = new Date(mon);
|
||||
d.setDate(mon.getDate() + i);
|
||||
return d.toISOString().split('T')[0];
|
||||
});
|
||||
}
|
||||
|
||||
const dates = getWeekDates();
|
||||
|
||||
const views = {
|
||||
simple: {
|
||||
templateId: 'simple',
|
||||
groupings: [{ type: 'date', values: dates }]
|
||||
},
|
||||
resource: {
|
||||
templateId: 'resource',
|
||||
groupings: [
|
||||
{ type: 'resource', values: ['alice', 'bob', 'carol'] },
|
||||
{ type: 'date', values: dates.slice(0, 3) }
|
||||
]
|
||||
},
|
||||
team: {
|
||||
templateId: 'team',
|
||||
groupings: [
|
||||
{ type: 'team', values: ['alpha', 'beta'] },
|
||||
{ type: 'resource', values: ['alice', 'bob', 'carol', 'dave'], parentKey: 'teamId' },
|
||||
{ type: 'date', values: dates.slice(0, 3) }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function generateTimeAxis() {
|
||||
const el = document.getElementById('time-axis');
|
||||
el.innerHTML = Array.from({ length: 15 }, (_, i) =>
|
||||
`<swp-hour-marker>${(6 + i).toString().padStart(2, '0')}:00</swp-hour-marker>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
async function render(view, label) {
|
||||
viewInfo.textContent = label;
|
||||
await orchestrator.render(view, container);
|
||||
}
|
||||
|
||||
document.getElementById('btn-simple').onclick = () => render(views.simple, '5 datoer');
|
||||
document.getElementById('btn-resource').onclick = () => render(views.resource, '3 resources × 3 datoer');
|
||||
document.getElementById('btn-team').onclick = () => render(views.team, '2 teams × 2 resources × 3 datoer');
|
||||
|
||||
generateTimeAxis();
|
||||
render(views.simple, '5 datoer');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue