feat: bulk delete, team dispatch, calendar views, timeline colors
Deploy / deploy (push) Successful in 11s

- Multi-select bulk delete in all 5 list views (Artefacts, Posts, Tasks,
  Issues, Assets) with cascade deletes and confirmation modals
- Team-based issue dispatch: team picker on public issue form, team filter
  on Issues page, copy public link from Team page and Issues header,
  team assignment in IssueDetailPanel
- Month/Week toggle on PostCalendar and TaskCalendarView
- Month/Week/Day zoom on project and campaign timelines (InteractiveTimeline)
  and ProjectDetail GanttView, with Month as default
- Custom timeline bar colors: clickable color dot with 12-color palette
  popover on project, campaign, and task timeline bars
- Artefacts default view changed to list
- BulkSelectBar reusable component
- i18n keys for all new features (en + ar)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-01 14:55:36 +03:00
parent 20d76dea8b
commit 42a5f17d0b
40 changed files with 3050 additions and 1625 deletions
+15 -15
View File
@@ -34,7 +34,7 @@ export default function Login() {
await login(email, password)
navigate('/')
} catch (err) {
setError(err.message || 'Invalid email or password')
setError(err.message || t('login.invalidCredentials'))
} finally {
setLoading(false)
}
@@ -44,7 +44,7 @@ export default function Login() {
e.preventDefault()
setError('')
if (setupPassword !== setupConfirm) {
setError('Passwords do not match')
setError(t('login.passwordMismatch'))
return
}
setLoading(true)
@@ -55,7 +55,7 @@ export default function Login() {
setNeedsSetup(false)
setEmail(setupEmail)
} catch (err) {
setError(err.message || 'Setup failed')
setError(err.message || t('login.setupFailed'))
} finally {
setLoading(false)
}
@@ -78,10 +78,10 @@ export default function Login() {
<Megaphone className="w-8 h-8 text-white" />
</div>
<h1 className="text-3xl font-bold text-white mb-2">
{needsSetup ? 'Initial Setup' : t('login.title')}
{needsSetup ? t('login.initialSetup') : t('login.title')}
</h1>
<p className="text-slate-400">
{needsSetup ? 'Create your superadmin account to get started' : t('login.subtitle')}
{needsSetup ? t('login.initialSetupDesc') : t('login.subtitle')}
</p>
</div>
@@ -89,7 +89,7 @@ export default function Login() {
{setupDone && (
<div className="flex items-center gap-2 p-3 mb-4 bg-green-500/10 border border-green-500/30 rounded-lg">
<CheckCircle className="w-5 h-5 text-green-400 shrink-0" />
<p className="text-sm text-green-400">Account created. You can now log in.</p>
<p className="text-sm text-green-400">{t('login.accountCreated')}</p>
</div>
)}
@@ -99,7 +99,7 @@ export default function Login() {
<form onSubmit={handleSetup} className="space-y-5">
{/* Name */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Name</label>
<label className="block text-sm font-medium text-slate-300 mb-2">{t('login.fullName')}</label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<input
@@ -107,7 +107,7 @@ export default function Login() {
value={setupName}
onChange={(e) => setSetupName(e.target.value)}
className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
placeholder="Your name"
placeholder={t('login.fullNamePlaceholder')}
required
autoFocus
/>
@@ -116,7 +116,7 @@ export default function Login() {
{/* Email */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Email</label>
<label className="block text-sm font-medium text-slate-300 mb-2">{t('login.email')}</label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<input
@@ -133,7 +133,7 @@ export default function Login() {
{/* Password */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Password</label>
<label className="block text-sm font-medium text-slate-300 mb-2">{t('login.password')}</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<input
@@ -141,7 +141,7 @@ export default function Login() {
value={setupPassword}
onChange={(e) => setSetupPassword(e.target.value)}
className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
placeholder="Choose a strong password"
placeholder={t('login.passwordPlaceholder')}
required
minLength={6}
/>
@@ -150,7 +150,7 @@ export default function Login() {
{/* Confirm Password */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Confirm Password</label>
<label className="block text-sm font-medium text-slate-300 mb-2">{t('login.confirmPassword')}</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<input
@@ -158,7 +158,7 @@ export default function Login() {
value={setupConfirm}
onChange={(e) => setSetupConfirm(e.target.value)}
className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
placeholder="Re-enter your password"
placeholder={t('login.confirmPasswordPlaceholder')}
required
minLength={6}
/>
@@ -182,10 +182,10 @@ export default function Login() {
{loading ? (
<span className="flex items-center justify-center gap-2">
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
Creating account...
{t('login.creatingAccount')}
</span>
) : (
'Create Superadmin Account'
t('login.createAccount')
)}
</button>
</form>