aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/components/SyncStatusIndicator.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-07 23:34:03 +0900
committernsfisis <nsfisis@gmail.com>2025-12-07 23:34:03 +0900
commit0c042ac89fc0822fcbe09c48702857faa5494ae1 (patch)
treeea1f1d180f747613343040d441a07f92b2760840 /src/client/components/SyncStatusIndicator.tsx
parentae5a0bb97fbf013417a6962f7e077f0408b2a951 (diff)
downloadkioku-0c042ac89fc0822fcbe09c48702857faa5494ae1.tar.gz
kioku-0c042ac89fc0822fcbe09c48702857faa5494ae1.tar.zst
kioku-0c042ac89fc0822fcbe09c48702857faa5494ae1.zip
feat(client): add sync status indicator component
Add SyncStatusIndicator component to display current sync state in the UI header. The component shows online/offline status, syncing progress, pending changes count, and sync errors. - Create SyncProvider context to wrap SyncManager for React components - Add SyncStatusIndicator component with visual status indicators - Integrate indicator into HomePage header - Add comprehensive tests for SyncStatusIndicator and SyncProvider - Update existing tests to include SyncProvider wrapper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/client/components/SyncStatusIndicator.tsx')
-rw-r--r--src/client/components/SyncStatusIndicator.tsx82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/client/components/SyncStatusIndicator.tsx b/src/client/components/SyncStatusIndicator.tsx
new file mode 100644
index 0000000..23e3ec6
--- /dev/null
+++ b/src/client/components/SyncStatusIndicator.tsx
@@ -0,0 +1,82 @@
+import { useSync } from "../stores";
+import { SyncStatus } from "../sync";
+
+export function SyncStatusIndicator() {
+ const { isOnline, isSyncing, pendingCount, lastError, status } = useSync();
+
+ const getStatusText = (): string => {
+ if (!isOnline) {
+ return "Offline";
+ }
+ if (isSyncing) {
+ return "Syncing...";
+ }
+ if (status === SyncStatus.Error && lastError) {
+ return "Sync error";
+ }
+ if (pendingCount > 0) {
+ return `${pendingCount} pending`;
+ }
+ return "Synced";
+ };
+
+ const getStatusColor = (): string => {
+ if (!isOnline) {
+ return "#6c757d"; // gray
+ }
+ if (isSyncing) {
+ return "#007bff"; // blue
+ }
+ if (status === SyncStatus.Error) {
+ return "#dc3545"; // red
+ }
+ if (pendingCount > 0) {
+ return "#ffc107"; // yellow
+ }
+ return "#28a745"; // green
+ };
+
+ const getStatusIcon = (): string => {
+ if (!isOnline) {
+ return "\u25CB"; // hollow circle
+ }
+ if (isSyncing) {
+ return "\u21BB"; // rotating arrows
+ }
+ if (status === SyncStatus.Error) {
+ return "\u2717"; // cross mark
+ }
+ if (pendingCount > 0) {
+ return "\u25D4"; // partial circle
+ }
+ return "\u2713"; // check mark
+ };
+
+ return (
+ <div
+ data-testid="sync-status-indicator"
+ style={{
+ display: "inline-flex",
+ alignItems: "center",
+ gap: "0.25rem",
+ padding: "0.25rem 0.5rem",
+ borderRadius: "4px",
+ backgroundColor: "#f8f9fa",
+ border: "1px solid #dee2e6",
+ fontSize: "0.875rem",
+ }}
+ title={lastError || undefined}
+ >
+ <span
+ style={{
+ color: getStatusColor(),
+ fontWeight: "bold",
+ }}
+ aria-hidden="true"
+ >
+ {getStatusIcon()}
+ </span>
+ <span style={{ color: getStatusColor() }}>{getStatusText()}</span>
+ </div>
+ );
+}