git activity update

This commit is contained in:
2025-10-15 14:22:03 +02:00
parent f830e4fccf
commit 9041c7db94
5 changed files with 470 additions and 2 deletions

View File

@@ -1,12 +1,13 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { ArrowLeft, Moon, Sun, Bell, Download, Upload, Trash2 } from 'lucide-react';
import { ArrowLeft, Moon, Sun, Bell, Download, Upload, Trash2, Plus, Trash, GitBranch } from 'lucide-react';
import { Button } from '../components/ui/button';
import { Switch } from '../components/ui/switch';
import { Label } from '../components/ui/label';
import { useToast } from '../components/ui/use-toast';
import { exportData, importData, clearAllData } from '../lib/storage';
import { addIntegration, getIntegrations, removeIntegration, getGitEnabled, setGitEnabled, fetchAllGitActivity, getCachedGitActivity } from '../lib/git';
const SettingsPage = () => {
const navigate = useNavigate();
@@ -15,6 +16,11 @@ const SettingsPage = () => {
return localStorage.getItem('theme') === 'dark';
});
const [notifications, setNotifications] = useState(false);
const [gitEnabled, setGitEnabledState] = useState(getGitEnabled());
const [sources, setSources] = useState(() => getIntegrations());
const [form, setForm] = useState({ provider: 'github', baseUrl: '', username: '', token: '' });
const [{ lastSync }, setCacheInfo] = useState(getCachedGitActivity());
const [syncing, setSyncing] = useState(false);
useEffect(() => {
if (darkMode) {
@@ -30,6 +36,31 @@ const SettingsPage = () => {
setDarkMode(enabled);
};
const toggleGitEnabled = (enabled) => {
setGitEnabledState(enabled);
setGitEnabled(enabled);
};
const handleAddSource = async () => {
if (!form.username) return;
const baseUrl = form.baseUrl || (form.provider === 'github' ? 'https://api.github.com' : form.provider === 'gitlab' ? 'https://gitlab.com' : '');
await addIntegration({ provider: form.provider, baseUrl, username: form.username, token: form.token });
setSources(getIntegrations());
setForm({ provider: 'github', baseUrl: '', username: '', token: '' });
};
const handleRemoveSource = (id) => {
removeIntegration(id);
setSources(getIntegrations());
};
const handleSyncGit = async () => {
setSyncing(true);
const data = await fetchAllGitActivity({ force: true });
setCacheInfo(data);
setSyncing(false);
};
const handleExport = () => {
const data = exportData();
const blob = new Blob([data], { type: 'application/json' });
@@ -118,6 +149,71 @@ const SettingsPage = () => {
</motion.div>
<div className="space-y-4">
{/* Integrations */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.05 }}
className="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-sm border border-slate-200 dark:border-slate-700"
>
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2"><GitBranch className="w-4 h-4" /> Integrations</h2>
<div className="flex items-center justify-between mb-4">
<div>
<Label htmlFor="git-enabled" className="text-base">Show Git Activity</Label>
<p className="text-sm text-muted-foreground">Display a unified Git activity grid</p>
</div>
<Switch id="git-enabled" checked={gitEnabled} onCheckedChange={toggleGitEnabled} />
</div>
<div className="grid sm:grid-cols-4 gap-2 mb-3">
<div>
<Label className="text-xs">Provider</Label>
<select className="w-full bg-transparent border rounded-md p-2" value={form.provider} onChange={e => setForm({ ...form, provider: e.target.value })}>
<option value="github">GitHub</option>
<option value="gitlab">GitLab</option>
<option value="gitea">Gitea</option>
<option value="forgejo">Forgejo</option>
<option value="custom">Custom</option>
</select>
</div>
<div>
<Label className="text-xs">Base URL</Label>
<input className="w-full bg-transparent border rounded-md p-2" placeholder="e.g. https://api.github.com" value={form.baseUrl} onChange={e => setForm({ ...form, baseUrl: e.target.value })} />
</div>
<div>
<Label className="text-xs">Username</Label>
<input className="w-full bg-transparent border rounded-md p-2" placeholder="your-username" value={form.username} onChange={e => setForm({ ...form, username: e.target.value })} />
</div>
<div>
<Label className="text-xs">Token</Label>
<input className="w-full bg-transparent border rounded-md p-2" placeholder="personal access token" value={form.token} onChange={e => setForm({ ...form, token: e.target.value })} />
</div>
</div>
<Button onClick={handleAddSource} className="mb-4 rounded-full"><Plus className="w-4 h-4 mr-1" /> Add Source</Button>
<div className="flex items-center justify-between mt-2">
<Button variant="outline" onClick={handleSyncGit} disabled={syncing} className="rounded-full">
{syncing ? 'Syncing…' : 'Sync Git Data'}
</Button>
<span className="text-xs text-muted-foreground">{lastSync ? `Last sync: ${new Date(lastSync).toLocaleString()}` : ''}</span>
</div>
{sources.length > 0 && (
<div className="space-y-2">
{sources.map(src => (
<div key={src.id} className="flex items-center justify-between bg-slate-50/50 dark:bg-slate-900/30 border border-slate-200 dark:border-slate-700 rounded-md p-2">
<div className="text-sm">
<div className="font-medium">{src.provider} {src.username}</div>
<div className="text-xs text-muted-foreground">{src.baseUrl}</div>
</div>
<Button size="icon" variant="ghost" onClick={() => handleRemoveSource(src.id)} className="rounded-full" aria-label="Remove Source">
<Trash className="w-4 h-4" />
</Button>
</div>
))}
</div>
)}
</motion.div>
{/* Appearance */}
<motion.div
initial={{ opacity: 0, y: 20 }}