feat: introduce Teams & OKRs feature with models, types, and UI components for team management and objective tracking
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12m53s

This commit is contained in:
Julien Froidefond
2026-01-07 10:11:59 +01:00
parent e3a47dd7e5
commit 5f661c8bfd
35 changed files with 3993 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import { forwardRef, SelectHTMLAttributes } from 'react';
interface SelectOption {
value: string;
label: string;
disabled?: boolean;
}
interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'children'> {
label?: string;
error?: string;
options: SelectOption[];
placeholder?: string;
}
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ className = '', label, error, id, options, placeholder, ...props }, ref) => {
const selectId = id || props.name;
return (
<div className="w-full">
{label && (
<label htmlFor={selectId} className="mb-2 block text-sm font-medium text-foreground">
{label}
</label>
)}
<div className="relative">
<select
ref={ref}
id={selectId}
className={`
w-full appearance-none rounded-lg border bg-input px-4 py-2.5 pr-10 text-foreground
placeholder:text-muted-foreground
focus:outline-none focus:ring-2 focus:ring-primary/20
disabled:cursor-not-allowed disabled:opacity-50
${error ? 'border-destructive focus:border-destructive' : 'border-input-border focus:border-primary'}
${className}
`}
{...props}
>
{placeholder && (
<option value="" disabled={props.required}>
{placeholder}
</option>
)}
{options.map((option) => (
<option key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</option>
))}
</select>
{/* Custom arrow icon */}
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2">
<svg
className="h-5 w-5 text-muted-foreground"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
{error && <p className="mt-1.5 text-sm text-destructive">{error}</p>}
</div>
);
}
);
Select.displayName = 'Select';

View File

@@ -0,0 +1,46 @@
'use client';
import { ReactNode } from 'react';
export interface ToggleOption<T extends string> {
value: T;
label: string;
icon?: ReactNode;
}
interface ToggleGroupProps<T extends string> {
value: T;
options: ToggleOption<T>[];
onChange: (value: T) => void;
className?: string;
}
export function ToggleGroup<T extends string>({
value,
options,
onChange,
className = '',
}: ToggleGroupProps<T>) {
return (
<div className={`flex items-center gap-2 rounded-lg border border-border bg-card p-1 ${className}`}>
{options.map((option) => (
<button
key={option.value}
type="button"
onClick={() => onChange(option.value)}
className={`
flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors
${value === option.value
? 'bg-[#8b5cf6] text-white shadow-sm'
: 'text-muted hover:text-foreground hover:bg-card-hover'
}
`}
>
{option.icon && <span className="flex items-center">{option.icon}</span>}
{option.label}
</button>
))}
</div>
);
}

View File

@@ -9,4 +9,7 @@ export { EditableMotivatorTitle } from './EditableMotivatorTitle';
export { EditableYearReviewTitle } from './EditableYearReviewTitle';
export { Input } from './Input';
export { Modal, ModalFooter } from './Modal';
export { Select } from './Select';
export { Textarea } from './Textarea';
export { ToggleGroup } from './ToggleGroup';
export type { ToggleOption } from './ToggleGroup';