nordabiz/mockups/messages_chat_view.html
Maciej Pienczyn 110d971dca
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat: migrate prod docs to OVH VPS + UTC→Warsaw timezone in all templates
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS
(57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash
commands, memory files, architecture docs, and deploy procedures.

Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted
155 .strftime() calls across 71 templates so timestamps display
in Polish timezone regardless of server timezone.

Also includes: created_by_id tracking, abort import fix, ICS
calendar fix for missing end times, Pros Poland data cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:41:53 +02:00

828 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NordaBiznes — Wiadomości (mockup konwersacyjny)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300..700;1,9..40,300..700&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f5f6f8;
--panel: #ffffff;
--border: #e4e7ec;
--text: #1a1d23;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
--accent: #2563eb;
--accent-light: #eff4ff;
--accent-hover: #1d4ed8;
--bubble-mine: #2563eb;
--bubble-mine-text: #ffffff;
--bubble-theirs: #f0f1f3;
--bubble-theirs-text: #1a1d23;
--unread: #ef4444;
--online: #22c55e;
--hover: #f8f9fb;
--active: #eff4ff;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--radius: 12px;
--radius-sm: 8px;
}
body {
font-family: 'DM Sans', -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
height: 100vh;
overflow: hidden;
}
/* ── Top bar (simulating portal nav) ── */
.topbar {
height: 56px;
background: #1e3a5f;
display: flex;
align-items: center;
padding: 0 24px;
gap: 16px;
}
.topbar-logo {
color: white;
font-weight: 700;
font-size: 16px;
letter-spacing: -0.3px;
}
.topbar-logo span { color: #60a5fa; }
.topbar-nav {
display: flex;
gap: 4px;
margin-left: 32px;
}
.topbar-nav a {
color: rgba(255,255,255,0.65);
text-decoration: none;
font-size: 13.5px;
padding: 6px 14px;
border-radius: 6px;
transition: all 0.15s;
font-weight: 500;
}
.topbar-nav a:hover { color: white; background: rgba(255,255,255,0.08); }
.topbar-nav a.active {
color: white;
background: rgba(255,255,255,0.12);
}
.topbar-badge {
background: var(--unread);
color: white;
font-size: 10px;
font-weight: 700;
padding: 1px 5px;
border-radius: 10px;
margin-left: 4px;
vertical-align: top;
}
/* ── Main layout ── */
.messages-container {
display: flex;
height: calc(100vh - 56px);
}
/* ── Left panel: conversation list ── */
.conversations-panel {
width: 380px;
min-width: 380px;
background: var(--panel);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
}
.conversations-header {
padding: 20px 20px 12px;
border-bottom: 1px solid var(--border);
}
.conversations-header h2 {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.4px;
margin-bottom: 14px;
}
.search-box {
position: relative;
}
.search-box input {
width: 100%;
padding: 9px 12px 9px 36px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: 13.5px;
font-family: inherit;
background: var(--bg);
color: var(--text);
outline: none;
transition: border-color 0.15s;
}
.search-box input:focus { border-color: var(--accent); }
.search-box input::placeholder { color: var(--text-muted); }
.search-box svg {
position: absolute;
left: 11px;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.new-message-btn {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
background: var(--accent);
color: white;
border: none;
width: 30px;
height: 30px;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s;
}
.new-message-btn:hover { background: var(--accent-hover); }
.conversation-list {
flex: 1;
overflow-y: auto;
padding: 6px 8px;
}
.conversation-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.12s;
position: relative;
}
.conversation-item:hover { background: var(--hover); }
.conversation-item.active { background: var(--active); }
.conversation-item.unread .conv-name { font-weight: 700; }
.conversation-item.unread .conv-preview { color: var(--text); font-weight: 500; }
.conv-avatar {
width: 46px;
height: 46px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 16px;
color: white;
flex-shrink: 0;
position: relative;
}
.conv-avatar.green { background: #059669; }
.conv-avatar.blue { background: #2563eb; }
.conv-avatar.purple { background: #7c3aed; }
.conv-avatar.orange { background: #ea580c; }
.conv-avatar.teal { background: #0d9488; }
.conv-avatar.rose { background: #e11d48; }
.conv-avatar.group { background: #475569; font-size: 14px; }
.conv-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.conv-avatar .online-dot {
position: absolute;
bottom: 1px;
right: 1px;
width: 11px;
height: 11px;
background: var(--online);
border: 2px solid var(--panel);
border-radius: 50%;
}
.conv-content {
flex: 1;
min-width: 0;
}
.conv-top {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 3px;
}
.conv-name {
font-size: 14px;
font-weight: 600;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conv-time {
font-size: 11.5px;
color: var(--text-muted);
white-space: nowrap;
margin-left: 8px;
flex-shrink: 0;
}
.conversation-item.unread .conv-time { color: var(--accent); font-weight: 600; }
.conv-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.conv-preview {
font-size: 13px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.35;
}
.conv-preview .you { color: var(--text-muted); }
.unread-badge {
background: var(--accent);
color: white;
font-size: 10.5px;
font-weight: 700;
min-width: 20px;
height: 20px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
flex-shrink: 0;
margin-left: 8px;
}
.conv-group-tag {
font-size: 11px;
color: var(--text-muted);
margin-left: 6px;
font-weight: 400;
}
/* ── Right panel: chat view ── */
.chat-panel {
flex: 1;
display: flex;
flex-direction: column;
background: var(--bg);
}
.chat-header {
background: var(--panel);
border-bottom: 1px solid var(--border);
padding: 14px 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-header-left {
display: flex;
align-items: center;
gap: 12px;
}
.chat-header-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
color: white;
background: #059669;
}
.chat-header-info h3 {
font-size: 15px;
font-weight: 650;
letter-spacing: -0.2px;
}
.chat-header-info .subtitle {
font-size: 12px;
color: var(--text-muted);
margin-top: 1px;
}
.chat-header-actions {
display: flex;
gap: 4px;
}
.chat-header-actions button {
background: none;
border: 1px solid var(--border);
color: var(--text-secondary);
width: 36px;
height: 36px;
border-radius: var(--radius-sm);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.12s;
}
.chat-header-actions button:hover {
background: var(--hover);
color: var(--text);
}
/* ── Messages area ── */
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 24px 24px 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.date-separator {
text-align: center;
margin: 16px 0;
position: relative;
}
.date-separator span {
background: var(--bg);
padding: 0 14px;
font-size: 11.5px;
color: var(--text-muted);
font-weight: 500;
position: relative;
z-index: 1;
}
.date-separator::before {
content: '';
position: absolute;
top: 50%;
left: 10%;
right: 10%;
height: 1px;
background: var(--border);
}
.message-row {
display: flex;
margin-bottom: 2px;
}
.message-row.mine { justify-content: flex-end; }
.message-row.theirs { justify-content: flex-start; }
.message-bubble {
max-width: 520px;
padding: 10px 14px;
font-size: 14px;
line-height: 1.5;
position: relative;
word-wrap: break-word;
}
.message-row.mine .message-bubble {
background: var(--bubble-mine);
color: var(--bubble-mine-text);
border-radius: 16px 16px 4px 16px;
}
.message-row.theirs .message-bubble {
background: var(--bubble-theirs);
color: var(--bubble-theirs-text);
border-radius: 16px 16px 16px 4px;
}
/* consecutive messages get flat edges */
.message-row.mine + .message-row.mine .message-bubble {
border-radius: 16px 4px 4px 16px;
}
.message-row.mine:has(+ .message-row.mine) .message-bubble {
border-radius: 16px 16px 4px 16px;
}
.message-row.theirs + .message-row.theirs .message-bubble {
border-radius: 16px 16px 16px 4px;
}
.message-time {
font-size: 11px;
margin-top: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.message-row.mine .message-time {
color: rgba(255,255,255,0.6);
justify-content: flex-end;
}
.message-row.theirs .message-time {
color: var(--text-muted);
}
.read-check {
color: rgba(255,255,255,0.6);
}
.read-check.read { color: #93c5fd; }
.message-subject {
font-size: 11.5px;
font-weight: 600;
opacity: 0.7;
margin-bottom: 4px;
letter-spacing: 0.2px;
text-transform: uppercase;
}
.message-row.theirs .message-subject { color: var(--text-muted); }
/* ── Input area ── */
.chat-input-area {
background: var(--panel);
border-top: 1px solid var(--border);
padding: 16px 24px;
}
.chat-input-wrapper {
display: flex;
align-items: flex-end;
gap: 10px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 8px 12px;
transition: border-color 0.15s;
}
.chat-input-wrapper:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.08);
}
.chat-input-wrapper textarea {
flex: 1;
border: none;
background: none;
font-family: inherit;
font-size: 14px;
color: var(--text);
resize: none;
outline: none;
min-height: 22px;
max-height: 120px;
line-height: 1.5;
}
.chat-input-wrapper textarea::placeholder { color: var(--text-muted); }
.input-actions {
display: flex;
gap: 2px;
align-items: center;
}
.input-actions button {
background: none;
border: none;
color: var(--text-muted);
width: 32px;
height: 32px;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.12s;
}
.input-actions button:hover { color: var(--text-secondary); background: rgba(0,0,0,0.04); }
.send-btn {
background: var(--accent) !important;
color: white !important;
border-radius: 8px !important;
width: 36px !important;
height: 32px !important;
}
.send-btn:hover { background: var(--accent-hover) !important; }
/* ── Empty state (no conversation selected) ── */
.chat-empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text-muted);
gap: 12px;
}
.chat-empty svg { opacity: 0.3; }
.chat-empty p { font-size: 14px; }
/* ── Scrollbar ── */
.conversation-list::-webkit-scrollbar,
.chat-messages::-webkit-scrollbar {
width: 6px;
}
.conversation-list::-webkit-scrollbar-thumb,
.chat-messages::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.12);
border-radius: 3px;
}
/* ── Watermark ── */
.mockup-badge {
position: fixed;
bottom: 12px;
right: 12px;
background: rgba(0,0,0,0.7);
color: white;
font-size: 11px;
padding: 4px 10px;
border-radius: 20px;
font-weight: 500;
z-index: 100;
pointer-events: none;
}
</style>
</head>
<body>
<!-- Top bar -->
<div class="topbar">
<div class="topbar-logo">Norda<span>Biznes</span></div>
<nav class="topbar-nav">
<a href="#">Firmy</a>
<a href="#">Forum</a>
<a href="#">Kalendarz</a>
<a href="#" class="active">Wiadomości<span class="topbar-badge">2</span></a>
<a href="#">NordaGPT</a>
</nav>
</div>
<div class="messages-container">
<!-- Left: Conversation list -->
<div class="conversations-panel">
<div class="conversations-header">
<h2>Wiadomości</h2>
<div class="search-box">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" placeholder="Szukaj rozmów...">
<button class="new-message-btn" title="Nowa wiadomość">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
</button>
</div>
</div>
<div class="conversation-list">
<!-- Active conversation: Magdalena -->
<div class="conversation-item active unread" onclick="selectConv(this)">
<div class="conv-avatar green">MK</div>
<div class="conv-content">
<div class="conv-top">
<span class="conv-name">Magdalena Kloska</span>
<span class="conv-time">11:54</span>
</div>
<div class="conv-bottom">
<span class="conv-preview">witam, weszlam w skladki i mam pytanie...</span>
<span class="unread-badge">2</span>
</div>
</div>
</div>
<!-- Group conversation -->
<div class="conversation-item" onclick="selectConv(this)">
<div class="conv-avatar group">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
</div>
<div class="conv-content">
<div class="conv-top">
<span class="conv-name">Modul skladek<span class="conv-group-tag">7 os.</span></span>
<span class="conv-time">24.03</span>
</div>
<div class="conv-bottom">
<span class="conv-preview"><span class="you">Ty: </span>Jest gotowa taka funkcjonalnosc dla roli kierownika...</span>
</div>
</div>
</div>
<!-- Artur -->
<div class="conversation-item" onclick="selectConv(this)">
<div class="conv-avatar blue">AW</div>
<div class="conv-content">
<div class="conv-top">
<span class="conv-name">Artur Wiertel</span>
<span class="conv-time">20.03</span>
</div>
<div class="conv-bottom">
<span class="conv-preview">fajnie to wyglada.</span>
</div>
</div>
</div>
<!-- Roman -->
<div class="conversation-item" onclick="selectConv(this)">
<div class="conv-avatar purple">RW</div>
<div class="conv-content">
<div class="conv-top">
<span class="conv-name">Roman Wiercinski</span>
<span class="conv-time">18.03</span>
</div>
<div class="conv-bottom">
<span class="conv-preview"><span class="you">Ty: </span>Dzien dobry, przesylam podsumowanie...</span>
</div>
</div>
</div>
<!-- Leszek -->
<div class="conversation-item" onclick="selectConv(this)">
<div class="conv-avatar teal">LG</div>
<div class="conv-content">
<div class="conv-top">
<span class="conv-name">Leszek Glaza</span>
<span class="conv-time">15.03</span>
</div>
<div class="conv-bottom">
<span class="conv-preview"><span class="you">Ty: </span>Lista firm z kontaktami gotowa.</span>
</div>
</div>
</div>
</div>
</div>
<!-- Right: Chat view -->
<div class="chat-panel">
<div class="chat-header">
<div class="chat-header-left">
<div class="chat-header-avatar">MK</div>
<div class="chat-header-info">
<h3>Magdalena Kloska</h3>
<div class="subtitle">Kierownik Biura &middot; Izba Norda Biznes</div>
</div>
</div>
<div class="chat-header-actions">
<button title="Szukaj w rozmowie">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
</button>
<button title="Wiecej">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
</button>
</div>
</div>
<div class="chat-messages">
<div class="date-separator"><span>19 marca 2026</span></div>
<!-- My message -->
<div class="message-row mine">
<div class="message-bubble">
<div class="message-subject">Skladki i NIP-y nowych czlonkow</div>
Pani Magdaleno, importuje dane o skladkach z pliku Excel do systemu portalu. Wiekszosc firm dopasowala sie automatycznie, ale kilka nazw wymaga weryfikacji.
Prosze o sprawdzenie — czy te firmy maja profile na portalu pod inna nazwa?
EKOZUK, FRESH BIKE, JANTAR, MACIEJ HALAS, MARKISOL, N33, PGK, SKLEPY LORD, WW GLASS
<div class="message-time">
19:49
<svg class="read-check read" width="16" height="12" viewBox="0 0 16 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 6l3.5 4L11 2"/><path d="M5 6l3.5 4L15 2"/></svg>
</div>
</div>
</div>
<div class="message-row mine">
<div class="message-bubble">
<div class="message-subject">Kalendarz — wydarzenia zewnetrzne</div>
Pani Magdo, dodalismy przed chwila tez informacje o wydarzeniach zewnetrznych. One w kalendarzu sa widoczne...
<div class="message-time">
12:06
<svg class="read-check read" width="16" height="12" viewBox="0 0 16 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 6l3.5 4L11 2"/><path d="M5 6l3.5 4L15 2"/></svg>
</div>
</div>
</div>
<div class="message-row theirs">
<div class="message-bubble">
z teog co widze na szybko i odnotowalam to firm ted jest w3pisana dwa razy ale nip ten sam tutaj...
<div class="message-time">13:30</div>
</div>
</div>
<div class="message-row mine">
<div class="message-bubble">
Ok, pani Magdo, ogarne te tematy, jak tylko wroce do biura.
<div class="message-time">
13:54
<svg class="read-check read" width="16" height="12" viewBox="0 0 16 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 6l3.5 4L11 2"/><path d="M5 6l3.5 4L15 2"/></svg>
</div>
</div>
</div>
<div class="date-separator"><span>25 marca 2026</span></div>
<div class="message-row theirs">
<div class="message-bubble">
dzien dobry do jutra wlacznie mam opieke ale staram sie ogrniac tematy ile sie da.
EKOZUK- EKOFABRYKA TO JEST TO SAMO
FRESH BIKE fresh bike oraz jantar- to jest podspolka da Eura Tech - te na niebiesko nie placa skladeek indywidualnych ale firma wchodzaca do Nordy moze wniesc kilka swoich spolek placac wieksza skladke.
MACIEJ HALAS- prosze ich nie uwzlgedniac - rezygnacja z czlonkostwa.
MARKISOL tak samo rezygnajca
N33 to tezs jest pod spolk awraz z Termo
PGK Pucka Gospodarka Komunalna Sp. z o.o. NIP: 587-02-00-062
SKLEPY LORD: Lord sp. z o.o. nip 5882533102
WW GLASS tak samo podspolka innej firmy glownje - tutaj lenap hale
<div class="message-time">11:47</div>
</div>
</div>
<div class="message-row theirs">
<div class="message-bubble">
witam, weszlam w skladki i mam pytanie jesli ktos np zaplaci zaleglosc lub kolejna rate jaqk to zmienic w systemie ?
<div class="message-time">11:54</div>
</div>
</div>
<div class="date-separator"><span>27 marca 2026</span></div>
<div class="message-row mine">
<div class="message-bubble">
Pani Magdaleno, informacje o firmach przyjete, wprowadzam poprawki w systemie.
Dwa pytania:
1. N33 i Termo — pod jaka firme glowna wchodza?
2. Potrzebuje NIP-y nowych czlonkow: Audioline, Coach 4 You, Digital Technik, Ekonsult, GoodWill, IBET, Prospoland, Steamset.
Co do skladek — panel Administracja, Skladki, przy danym miesiacu "Oznacz jako oplacone". Mozna wpisac kwote i date wplaty.
<div class="message-time">
11:07
<svg class="read-check" width="16" height="12" viewBox="0 0 16 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 6l3.5 4L11 2"/><path d="M5 6l3.5 4L15 2"/></svg>
</div>
</div>
</div>
</div>
<!-- Input -->
<div class="chat-input-area">
<div class="chat-input-wrapper">
<textarea rows="1" placeholder="Napisz wiadomosc..."></textarea>
<div class="input-actions">
<button title="Dolacz plik">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
</button>
<button class="send-btn" title="Wyslij">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="mockup-badge">MOCKUP — widok konwersacyjny</div>
<script>
function selectConv(el) {
document.querySelectorAll('.conversation-item').forEach(i => i.classList.remove('active'));
el.classList.add('active');
}
</script>
</body>
</html>