| @ -0,0 +1,134 @@ | |||||
| <?php | |||||
| namespace App\Controller; | |||||
| use App\Entity\Project; | |||||
| use App\Entity\Task; | |||||
| use App\Service\TaskLifecycleManager; | |||||
| use Doctrine\ORM\EntityManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||||
| use Symfony\Component\HttpFoundation\Response; | |||||
| use Symfony\Component\HttpFoundation\StreamedResponse; | |||||
| use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | |||||
| use Symfony\Component\Routing\Attribute\Route; | |||||
| #[Route('/badge')] | |||||
| class BadgeController extends AbstractController | |||||
| { | |||||
| #[Route('/project-status/{projectSlug}/{status}', name: 'app_badge_project_status')] | |||||
| public function index(EntityManagerInterface $entityManager, TaskLifecycleManager $taskLifecycleManager, string $projectSlug, string $status): Response | |||||
| { | |||||
| $project = $entityManager->getRepository(Project::class)->findOneBySlug($projectSlug); | |||||
| if (!$project) { | |||||
| throw new NotFoundHttpException('Project not found'); | |||||
| } | |||||
| $stats = $taskLifecycleManager->getProjectStats($project); | |||||
| if (!isset($stats[$status])) { | |||||
| throw new NotFoundHttpException('Status not found'); | |||||
| } | |||||
| $stats = $stats[$status]; | |||||
| $response = new StreamedResponse(); | |||||
| $response->headers->set('Content-Type', 'image/svg+xml'); | |||||
| $response->headers->set('Cache-Control', 'public, max-age=300'); | |||||
| $response->setCallback(function () use ($stats): void { | |||||
| $colors = [ | |||||
| 'primary' => '#0d6efd', // $blue; | |||||
| 'secondary' => '#6c757d', // $gray-600; | |||||
| 'success' => '#198754', // $green; | |||||
| 'info' => '#0dcaf0', // $cyan; | |||||
| 'warning' => '#ffc107', // $yellow; | |||||
| 'danger' => '#dc3545', // $red; | |||||
| ]; | |||||
| $xml = new \DOMDocument(); | |||||
| $left = $stats['title']; | |||||
| $leftColor = $colors['secondary']; | |||||
| $right = sprintf(' % 3.0f%%', $stats['percentage']); | |||||
| $rightColor = $colors[$stats['color']]; | |||||
| $charWidth = 7; | |||||
| $fontSize = 11; | |||||
| $totalHeight = 20; | |||||
| $radius = ceil($totalHeight / $charWidth); | |||||
| $verticalMargin = round(($totalHeight - $fontSize) / 2); | |||||
| $horizontalMargin = round($fontSize / 3); | |||||
| $totalWidth = round(strlen($left.$right) * $charWidth + 2 * $horizontalMargin); | |||||
| $middle = floor(strlen($left) * $charWidth); | |||||
| $svg = $xml->createElement('svg'); | |||||
| $svg->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns', 'http://www.w3.org/2000/svg'); | |||||
| $svg->setAttribute('width', $totalWidth); | |||||
| $svg->setAttribute('height', $totalHeight); | |||||
| $defs = $xml->createElement('defs'); | |||||
| $gradientId = 'a'; | |||||
| $gradient = $xml->createElement('linearGradient'); | |||||
| $gradient->setAttribute('id', $gradientId); | |||||
| $stop = $xml->createElement('stop'); | |||||
| $stop->setAttribute('offset', ($middle * 100 / $totalWidth).'%'); | |||||
| $stop->setAttribute('stop-color', $leftColor); | |||||
| $gradient->appendChild($stop); | |||||
| $stop = $xml->createElement('stop'); | |||||
| $stop->setAttribute('offset', ($middle * 100 / $totalWidth).'%'); | |||||
| $stop->setAttribute('stop-color', $rightColor); | |||||
| $gradient->appendChild($stop); | |||||
| $defs->appendChild($gradient); | |||||
| $svg->appendChild($defs); | |||||
| $rect = $xml->createElement('rect'); | |||||
| $rect->setAttribute('width', $totalWidth); | |||||
| $rect->setAttribute('height', $totalHeight); | |||||
| $rect->setAttribute('rx', $radius); | |||||
| $rect->setAttribute('style', sprintf('fill:url(#%s)', $gradientId)); | |||||
| $svg->appendChild($rect); | |||||
| $fontG = $xml->createElement('g'); | |||||
| $fontG->setAttribute('font-family', implode(',', ['DejaVu Sans', 'Verdana', 'Geneva', 'sans-serif'])); | |||||
| $fontG->setAttribute('font-size', $fontSize); | |||||
| $leftText = $xml->createElement('text', $left); | |||||
| $leftText->setAttribute('x', $horizontalMargin); | |||||
| $leftText->setAttribute('y', $totalHeight - $verticalMargin); | |||||
| $leftText->setAttribute('fill', '#000'); | |||||
| $fontG->appendChild($leftText); | |||||
| $leftText = $xml->createElement('text', $left); | |||||
| $leftText->setAttribute('x', $horizontalMargin); | |||||
| $leftText->setAttribute('y', $totalHeight - $verticalMargin - 1); | |||||
| $leftText->setAttribute('fill', '#fff'); | |||||
| $fontG->appendChild($leftText); | |||||
| $rightText = $xml->createElement('text', $right); | |||||
| $rightText->setAttribute('x', $totalWidth - $horizontalMargin); | |||||
| $rightText->setAttribute('y', $totalHeight - $verticalMargin); | |||||
| $rightText->setAttribute('style', 'text-anchor:end;fill:#000'); | |||||
| $fontG->appendChild($rightText); | |||||
| $rightText = $xml->createElement('text', $right); | |||||
| $rightText->setAttribute('x', $totalWidth - $horizontalMargin); | |||||
| $rightText->setAttribute('y', $totalHeight - $verticalMargin - 1); | |||||
| $rightText->setAttribute('style', 'text-anchor:end;fill:#fff'); | |||||
| $fontG->appendChild($rightText); | |||||
| $svg->appendChild($fontG); | |||||
| $xml->appendChild($svg); | |||||
| echo $xml->saveXML(); | |||||
| }); | |||||
| return $response; | |||||
| } | |||||
| } | |||||